Merge branch 'dev'

This commit is contained in:
Viktor Kovacs 2021-03-30 18:00:50 +02:00
commit 3c6e3c8ae2
50 changed files with 1049 additions and 536 deletions

View File

@ -21,6 +21,7 @@
"globals" : {
"OV" : true,
"THREE" : false,
"TextEncoder" : false,
"TextDecoder" : false,
"XMLHttpRequest" : false,
"FileReader" : false,

View File

@ -5,23 +5,29 @@ This repository contains the source code of the https://3dviewer.net website. Th
[![Build status](https://ci.appveyor.com/api/projects/status/exypq43a8kjby5n0?svg=true)](https://ci.appveyor.com/project/kovacsv/online3dviewer)
[![Build Status](https://travis-ci.com/kovacsv/Online3DViewer.svg?branch=master)](https://travis-ci.com/kovacsv/Online3DViewer)
## Documentation
- [User Documentation](https://3dviewer.net/info)
- [Developer Documentation](https://github.com/kovacsv/Online3DViewer/wiki)
## Supported file formats
### Import
- obj (with mtl and texture)
- 3ds (with texture)
- stl (ascii and binary)
- ply (ascii and binary)
- gltf (ascii and binary)
- off (ascii only)
- stl (text and binary)
- ply (text and binary)
- gltf (text and binary)
- off (text only)
### Export
- obj (with mtl)
- stl (ascii and binary)
- ply (ascii and binary)
- off (ascii only)
- stl (text and binary)
- ply (text and binary)
- gltf (text and binary)
- off (text only)
## Features

View File

@ -16,7 +16,7 @@ test_script:
- npm run test
artifacts:
- path: build\package\*
- path: build\final\package\*
deploy:
description: 'News:\n- ...'

View File

@ -1,7 +1,7 @@
{
"name": "online-3d-viewer",
"description": "Online 3D Viewer",
"version": "0.7.2",
"version": "0.7.3",
"repository": "github:kovacsv/Online3DViewer",
"license": "MIT",
"devDependencies": {

View File

@ -11,13 +11,13 @@ OV.Exporter = class
];
}
Export (model, format, extension, callbacks)
Export (model, format, extension)
{
let files = [];
for (let i = 0; i < this.exporters.length; i++) {
let exporter = this.exporters[i];
if (exporter.CanExport (format, extension)) {
exporter.Export (model, format, files, callbacks);
exporter.Export (model, format, files);
break;
}
}

View File

@ -42,7 +42,7 @@ OV.ExporterBase = class
{
constructor ()
{
this.callbacks = null;
}
CanExport (format, extension)
@ -50,9 +50,8 @@ OV.ExporterBase = class
return false;
}
Export (model, format, files, callbacks)
Export (model, format, files)
{
this.callbacks = callbacks;
this.ExportContent (model, format, files);
}

View File

@ -72,18 +72,31 @@ OV.ExporterGltf = class extends OV.ExporterBase
ExportBinaryContent (model, files)
{
function AlignToBoundary (size)
{
let remainder = size % 4;
if (remainder === 0) {
return size;
}
return size + (4 - remainder);
}
function WriteCharacters (writer, char, count)
{
for (let i = 0; i < count; i++) {
writer.WriteUnsignedCharacter8 (char);
}
}
let glbFile = new OV.ExportedFile ('model.glb');
files.push (glbFile);
let meshDataArr = this.GetMeshData (model);
let mainBuffer = this.GetMainBuffer (meshDataArr);
let mainJson = this.GetMainJson (meshDataArr);
mainJson.buffers.push ({
byteLength : mainBuffer.byteLength
});
let textureBuffers = [];
let obj = this;
let textureOffset = mainBuffer.byteLength;
let fileNameToIndex = [];
this.ExportMaterials (model, mainJson, function (texture) {
@ -91,20 +104,17 @@ OV.ExporterGltf = class extends OV.ExporterBase
let extension = OV.GetFileExtension (texture.name);
let textureIndex = fileNameToIndex[fileName];
if (textureIndex === undefined) {
let bufferIndex = mainJson.buffers.length;
let bufferViewIndex = mainJson.bufferViews.length;
textureIndex = mainJson.textures.length;
fileNameToIndex[fileName] = textureIndex;
let textureBuffer = obj.callbacks.getTextureBuffer (texture.name);
let textureBuffer = texture.buffer;
textureBuffers.push (textureBuffer);
mainJson.buffers.push ({
byteLength : textureBuffer.byteLength
});
mainJson.bufferViews.push ({
buffer : bufferIndex,
byteOffset : 0,
buffer : 0,
byteOffset : textureOffset,
byteLength : textureBuffer.byteLength
});
textureOffset += textureBuffer.byteLength;
mainJson.images.push ({
bufferView : bufferViewIndex,
mimeType : 'image/' + extension
@ -116,34 +126,42 @@ OV.ExporterGltf = class extends OV.ExporterBase
return textureIndex;
});
let mainJsonString = JSON.stringify (mainJson, null, 4);
let glbSize = 12 + 8 + mainJsonString.length;
for (let i = 0; i < mainJson.buffers.length; i++) {
glbSize += 8 + mainJson.buffers[i].byteLength;
let mainBinaryBufferLength = mainBuffer.byteLength;
for (let i = 0; i < textureBuffers.length; i++) {
let textureBuffer = textureBuffers[i];
mainBinaryBufferLength += textureBuffer.byteLength;
}
let mainBinaryBufferAlignedLength = AlignToBoundary (mainBinaryBufferLength);
mainJson.buffers.push ({
byteLength : mainBinaryBufferAlignedLength
});
let mainJsonString = JSON.stringify (mainJson);
let mainJsonBuffer = OV.Utf8StringToArrayBuffer (mainJsonString);
let mainJsonBufferLength = mainJsonBuffer.byteLength;
let mainJsonBufferAlignedLength = AlignToBoundary (mainJsonBufferLength);
let glbSize = 12 + 8 + mainJsonBufferAlignedLength + 8 + mainBinaryBufferAlignedLength;
let glbWriter = new OV.BinaryWriter (glbSize, true);
glbWriter.WriteUnsignedInteger32 (0x46546C67);
glbWriter.WriteUnsignedInteger32 (2);
glbWriter.WriteUnsignedInteger32 (glbSize);
glbWriter.WriteUnsignedInteger32 (mainJsonString.length);
glbWriter.WriteUnsignedInteger32 (mainJsonBufferAlignedLength);
glbWriter.WriteUnsignedInteger32 (0x4E4F534A);
for (let i = 0; i < mainJsonString.length; i++) {
glbWriter.WriteUnsignedCharacter8 (mainJsonString.charCodeAt (i));
}
glbWriter.WriteArrayBuffer (mainJsonBuffer);
WriteCharacters (glbWriter, 32, mainJsonBufferAlignedLength - mainJsonBufferLength);
glbWriter.WriteUnsignedInteger32 (mainBuffer.byteLength);
glbWriter.WriteUnsignedInteger32 (mainBinaryBufferAlignedLength);
glbWriter.WriteUnsignedInteger32 (0x004E4942);
glbWriter.WriteArrayBuffer (mainBuffer);
for (let i = 0; i < textureBuffers.length; i++) {
let textureBuffer = textureBuffers[i];
glbWriter.WriteUnsignedInteger32 (textureBuffer.byteLength);
glbWriter.WriteUnsignedInteger32 (0x004E4942);
glbWriter.WriteArrayBuffer (textureBuffer);
}
WriteCharacters (glbWriter, 0, mainBinaryBufferAlignedLength - mainBinaryBufferLength);
glbFile.SetContent (glbWriter.GetBuffer ());
}
@ -207,6 +225,16 @@ OV.ExporterGltf = class extends OV.ExporterBase
GetMainJson (meshDataArr)
{
function AddBufferView (mainJson, offset, length)
{
mainJson.bufferViews.push ({
buffer : 0,
byteOffset : offset,
byteLength : length,
});
return offset + length;
}
let mainJson = {
asset : {
version : '2.0'
@ -239,10 +267,14 @@ OV.ExporterGltf = class extends OV.ExporterBase
let primitives = meshData.buffer.primitives;
for (let primitiveIndex = 0; primitiveIndex < primitives.length; primitiveIndex++) {
let primitive = primitives[primitiveIndex];
let bufferViewIndex = mainJson.bufferViews.length;
let accessorIndex = mainJson.accessors.length;
let accessorOffset = 0;
let bufferViewIndex = mainJson.bufferViews.length;
let bufferViewOffset = meshData.offsets[primitiveIndex];
bufferViewOffset = AddBufferView (mainJson, bufferViewOffset, primitive.indices.length * this.components.index.size);
bufferViewOffset = AddBufferView (mainJson, bufferViewOffset, primitive.vertices.length * this.components.number.size);
bufferViewOffset = AddBufferView (mainJson, bufferViewOffset, primitive.normals.length * this.components.number.size);
let accessorIndex = mainJson.accessors.length;
let jsonPrimitive = {
attributes : {
POSITION : accessorIndex + 1,
@ -253,48 +285,39 @@ OV.ExporterGltf = class extends OV.ExporterBase
material : primitive.material
};
mainJson.bufferViews.push ({
buffer : 0,
byteOffset : meshData.offsets[primitiveIndex],
byteLength : meshData.sizes[primitiveIndex]
});
let bounds = primitive.GetBounds ();
mainJson.accessors.push ({
bufferView : bufferViewIndex,
byteOffset : accessorOffset,
byteOffset : 0,
componentType : this.components.index.type,
count : primitive.indices.length,
type : 'SCALAR'
});
accessorOffset += primitive.indices.length * this.components.index.size;
mainJson.accessors.push ({
bufferView : bufferViewIndex,
byteOffset : accessorOffset,
bufferView : bufferViewIndex + 1,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.vertices.length / 3,
min : bounds.min,
max : bounds.max,
type : 'VEC3'
});
accessorOffset += primitive.vertices.length * this.components.number.size;
mainJson.accessors.push ({
bufferView : bufferViewIndex,
byteOffset : accessorOffset,
bufferView : bufferViewIndex + 2,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.normals.length / 3,
type : 'VEC3'
});
accessorOffset += primitive.normals.length * this.components.number.size;
if (primitive.uvs.length > 0) {
bufferViewOffset = AddBufferView (mainJson, bufferViewOffset, primitive.uvs.length * this.components.number.size);
mainJson.accessors.push ({
bufferView : bufferViewIndex,
byteOffset : accessorOffset,
bufferView : bufferViewIndex + 3,
byteOffset : 0,
componentType : this.components.number.type,
count : primitive.uvs.length / 2,
type : 'VEC2'
});
accessorOffset += primitive.uvs.length * this.components.number.size;
jsonPrimitive.attributes.TEXCOORD_0 = accessorIndex + 3;
}
@ -331,7 +354,7 @@ OV.ExporterGltf = class extends OV.ExporterBase
function GetTextureParams (mainJson, texture, addTexture)
{
if (texture === null || texture.name === null || texture.url === null) {
if (texture === null || !texture.IsValid ()) {
return null;
}
@ -343,11 +366,18 @@ OV.ExporterGltf = class extends OV.ExporterBase
}
let textureIndex = addTexture (texture);
let textureParams = {
let textureParams = {
index : textureIndex
};
if (texture.HasTransformation ()) {
let extensionName = 'KHR_texture_transform';
if (mainJson.extensionsUsed === undefined) {
mainJson.extensionsUsed = [];
}
if (mainJson.extensionsUsed.indexOf (extensionName) === -1) {
mainJson.extensionsUsed.push (extensionName);
}
textureParams.extensions = {
KHR_texture_transform : {
offset : [texture.offset.x, -texture.offset.y],
@ -368,12 +398,11 @@ OV.ExporterGltf = class extends OV.ExporterBase
roughnessFactor : 1.0
},
emissiveFactor : ColorToRGB (material.emissive),
alphaMode : 'OPAQUE',
alphaCutoff : material.alphaTest
alphaMode : 'OPAQUE'
};
if (material.transparent) {
// TODO: mask?
// TODO: mask, alphaCutoff?
jsonMaterial.alphaMode = 'BLEND';
}

View File

@ -14,18 +14,19 @@ OV.ExporterObj = class extends OV.ExporterBase
{
function WriteTexture (mtlWriter, keyword, texture, files)
{
if (texture !== null && texture.name !== null && texture.url !== null) {
let fileName = OV.GetFileName (texture.name);
mtlWriter.WriteArrayLine ([keyword, fileName]);
if (texture === null || !texture.IsValid ()) {
return;
}
let fileName = OV.GetFileName (texture.name);
mtlWriter.WriteArrayLine ([keyword, fileName]);
let fileIndex = files.findIndex (function (file) {
return file.GetName () === fileName;
});
if (fileIndex === -1) {
let textureFile = new OV.ExportedFile (fileName);
textureFile.SetUrl (texture.url);
files.push (textureFile);
}
let fileIndex = files.findIndex (function (file) {
return file.GetName () === fileName;
});
if (fileIndex === -1) {
let textureFile = new OV.ExportedFile (fileName);
textureFile.SetUrl (texture.url);
files.push (textureFile);
}
}

View File

@ -15,7 +15,7 @@ OV.ConvertModelToThreeMeshes = function (model, callbacks)
function LoadTexture (threeMaterial, texture, onLoad)
{
if (texture === null || texture.url === null) {
if (texture === null || !texture.IsValid ()) {
return;
}
let loader = new THREE.TextureLoader ();

View File

@ -53,8 +53,8 @@ OV.ThreeModelLoader = class
obj.OnModelImported (importResult);
ready ();
},
error : function (importerError) {
obj.callbacks.onLoadError (importerError);
error : function (importError) {
obj.callbacks.onLoadError (importError);
obj.inProgress = false;
ready ();
}

View File

@ -179,14 +179,29 @@ OV.FileList = class
}
};
OV.ImporterErrorCode =
OV.RevokeModelUrls = function (model)
{
if (model === null) {
return;
}
for (let i = 0; i < model.MaterialCount (); i++) {
let material = model.GetMaterial (i);
material.EnumerateTextureMaps (function (texture) {
if (texture.url !== null) {
OV.RevokeObjectUrl (texture.url);
}
});
}
};
OV.ImportErrorCode =
{
NoImportableFile : 1,
ImportFailed : 2,
UnknownError : 3
};
OV.ImporterError = class
OV.ImportError = class
{
constructor (code, message)
{
@ -236,8 +251,8 @@ OV.Importer = class
Import (callbacks)
{
let mainFile = this.fileList.GetMainFile ();
if (mainFile === null || mainFile.file.content === null) {
callbacks.error (new OV.ImporterError (OV.ImporterErrorCode.NoImportableFile, null));
if (mainFile === null || mainFile.file === null || mainFile.file.content === null) {
callbacks.error (new OV.ImportError (OV.ImportErrorCode.NoImportableFile, null));
return;
}
@ -245,9 +260,6 @@ OV.Importer = class
result.mainFile = mainFile.file.name;
result.usedFiles.push (mainFile.file.name);
let fileNameToContent = {};
let textureNameToUrl = {};
let obj = this;
let importer = mainFile.importer;
importer.Import (mainFile.file.content, mainFile.file.extension, {
@ -256,47 +268,24 @@ OV.Importer = class
material.diffuse = new OV.Color (200, 200, 200);
return material;
},
getFileContent : function (filePath) {
let fileName = OV.GetFileName (filePath);
let fileContent = fileNameToContent[fileName];
if (fileContent === undefined) {
let file = obj.fileList.FindFileByPath (filePath);
if (file === null || file.content === null) {
result.missingFiles.push (fileName);
obj.missingFiles.push (fileName);
fileContent = null;
} else {
result.usedFiles.push (fileName);
fileContent = file.content;
}
fileNameToContent[fileName] = fileContent;
getFileBuffer : function (fileName) {
let fileBuffer = null;
let file = obj.fileList.FindFileByPath (fileName);
if (file === null || file.content === null) {
result.missingFiles.push (fileName);
obj.missingFiles.push (fileName);
fileBuffer = null;
} else {
result.usedFiles.push (fileName);
fileBuffer = file.content;
}
return fileContent;
},
getTextureObjectUrl : function (filePath) {
let fileName = OV.GetFileName (filePath);
let textureUrl = textureNameToUrl[fileName];
if (textureUrl === undefined) {
let file = obj.fileList.FindFileByPath (filePath);
if (file === null || file.content === null) {
result.missingFiles.push (fileName);
obj.missingFiles.push (fileName);
textureUrl = null;
} else {
result.usedFiles.push (fileName);
let blob = new Blob ([file.content]);
let blobURL = URL.createObjectURL (blob);
textureUrl = blobURL;
}
textureNameToUrl[fileName] = textureUrl;
}
return textureUrl;
return fileBuffer;
}
});
if (importer.IsError ()) {
let message = importer.GetMessage ();
callbacks.error (new OV.ImporterError (OV.ImporterErrorCode.ImportFailed, message));
callbacks.error (new OV.ImportError (OV.ImportErrorCode.ImportFailed, message));
return;
}
@ -350,5 +339,5 @@ OV.Importer = class
IsOnlyFileSource (source)
{
return this.fileList.IsOnlySource (source);
}
}
};

View File

@ -177,9 +177,12 @@ OV.Importer3ds = class extends OV.ImporterBase
this.ReadChunks (reader, endByte, function (chunkId, chunkLength) {
if (chunkId === OV.CHUNK3DS.MAT_TEXMAP_NAME) {
let textureName = obj.ReadName (reader);
let textureObjectUrl = obj.callbacks.getTextureObjectUrl (textureName);
let textureBuffer = obj.GetTextureBuffer (textureName);
texture.name = textureName;
texture.url = textureObjectUrl;
if (textureBuffer !== null) {
texture.url = textureBuffer.url;
texture.buffer = textureBuffer.buffer;
}
} else if (chunkId === OV.CHUNK3DS.MAT_TEXMAP_UOFFSET) {
texture.offset.x = reader.ReadFloat32 ();
} else if (chunkId === OV.CHUNK3DS.MAT_TEXMAP_VOFFSET) {
@ -336,7 +339,7 @@ OV.Importer3ds = class extends OV.ImporterBase
let v0 = reader.ReadUnsignedInteger16 ();
let v1 = reader.ReadUnsignedInteger16 ();
let v2 = reader.ReadUnsignedInteger16 ();
let flags = reader.ReadUnsignedInteger16 ();
reader.ReadUnsignedInteger16 (); // flags
mesh.AddTriangle (new OV.Triangle (v0, v1, v2));
}

View File

@ -1,18 +1,61 @@
OV.ImporterBuffers = class
{
constructor (getBufferCallback)
{
this.getBufferCallback = getBufferCallback;
this.fileBuffers = {};
this.textureBuffers = {};
}
GetFileBuffer (filePath)
{
let fileName = OV.GetFileName (filePath);
let buffer = this.fileBuffers[fileName];
if (buffer === undefined) {
buffer = this.getBufferCallback (fileName);
this.fileBuffers[fileName] = buffer;
}
return buffer;
}
GetTextureBuffer (filePath)
{
let fileName = OV.GetFileName (filePath);
let buffer = this.textureBuffers[fileName];
if (buffer === undefined) {
let texBuffer = this.getBufferCallback (fileName);
if (texBuffer !== null) {
buffer = {
url : OV.CreateObjectUrl (texBuffer),
buffer : texBuffer
};
} else {
buffer = null;
}
this.textureBuffers[fileName] = buffer;
}
return buffer;
}
};
OV.ImporterBase = class
{
constructor ()
{
this.extension = null;
this.callbacks = null;
this.buffers = null;
this.model = null;
this.error = null;
this.message = null;
this.buffers = null;
}
Import (content, extension, callbacks)
{
this.extension = extension;
this.callbacks = callbacks;
this.buffers = new OV.ImporterBuffers (this.callbacks.getFileBuffer);
this.model = new OV.Model ();
this.error = false;
this.message = null;
@ -31,6 +74,16 @@ OV.ImporterBase = class
OV.FinalizeModel (this.model, this.callbacks.getDefaultMaterial);
}
GetFileBuffer (filePath)
{
return this.buffers.GetFileBuffer (filePath);
}
GetTextureBuffer (filePath)
{
return this.buffers.GetTextureBuffer (filePath);
}
ResetState ()
{

View File

@ -351,12 +351,14 @@ OV.ImporterGltf = class extends OV.ImporterBase
this.bufferContents = null;
this.gltfExtensions = null;
this.imageIndexToTextureParams = null;
}
ResetState ()
{
this.bufferContents = [];
this.gltfExtensions = new OV.GltfExtensions ();
this.imageIndexToTextureParams = {};
}
CanImportExtension (extension)
@ -400,10 +402,13 @@ OV.ImporterGltf = class extends OV.ImporterBase
let buffer = null;
let gltfBuffer = gltf.buffers[i];
let base64Buffer = OV.Base64DataURIToArrayBuffer (gltfBuffer.uri);
if (base64Buffer === null) {
buffer = this.callbacks.getFileContent (gltfBuffer.uri);
} else {
if (base64Buffer !== null) {
buffer = base64Buffer.buffer;
} else {
let fileBuffer = this.GetFileBuffer (gltfBuffer.uri);
if (fileBuffer !== null) {
buffer = fileBuffer;
}
}
if (buffer === null) {
this.SetError ();
@ -613,31 +618,48 @@ OV.ImporterGltf = class extends OV.ImporterBase
let texture = new OV.TextureMap ();
let gltfTexture = gltf.textures[gltfTextureRef.index];
let gltfImage = gltf.images[gltfTexture.source];
let gltfImageIndex = gltfTexture.source;
let gltfImage = gltf.images[gltfImageIndex];
let textureIndexString = gltfTexture.source.toString ();
if (gltfImage.uri !== undefined) {
let base64Buffer = OV.Base64DataURIToArrayBuffer (gltfImage.uri);
if (base64Buffer !== null) {
let blob = new Blob ([base64Buffer.buffer], { type : base64Buffer.mimeType });
texture.name = 'Embedded_' + textureIndexString + GetTextureFileExtension (base64Buffer.mimeType);
texture.url = URL.createObjectURL (blob);
} else {
let textureObjectUrl = this.callbacks.getTextureObjectUrl (gltfImage.uri);
texture.name = gltfImage.uri;
texture.url = textureObjectUrl;
}
} else if (gltfImage.bufferView !== undefined) {
let bufferView = gltf.bufferViews[gltfImage.bufferView];
let reader = this.GetReaderFromBufferView (bufferView);
if (reader !== null) {
let buffer = reader.ReadArrayBuffer (bufferView.byteLength);
let blob = new Blob ([buffer], { type: gltfImage.mimeType });
texture.name = 'Binary_' + textureIndexString + GetTextureFileExtension (gltfImage.mimeType);
texture.url = URL.createObjectURL (blob);
let textureParams = this.imageIndexToTextureParams[gltfImageIndex];
if (textureParams === undefined) {
textureParams = {
name : null,
url : null,
buffer : null
};
let textureIndexString = gltfImageIndex.toString ();
if (gltfImage.uri !== undefined) {
let base64Buffer = OV.Base64DataURIToArrayBuffer (gltfImage.uri);
if (base64Buffer !== null) {
textureParams.name = 'Embedded_' + textureIndexString + GetTextureFileExtension (base64Buffer.mimeType);
textureParams.url = OV.CreateObjectUrlWithMimeType (base64Buffer.buffer, base64Buffer.mimeType);
textureParams.buffer = base64Buffer.buffer;
} else {
let textureBuffer = this.GetTextureBuffer (gltfImage.uri);
textureParams.name = gltfImage.uri;
if (textureBuffer !== null) {
textureParams.url = textureBuffer.url;
textureParams.buffer = textureBuffer.buffer;
}
}
} else if (gltfImage.bufferView !== undefined) {
let bufferView = gltf.bufferViews[gltfImage.bufferView];
let reader = this.GetReaderFromBufferView (bufferView);
if (reader !== null) {
let buffer = reader.ReadArrayBuffer (bufferView.byteLength);
textureParams.name = 'Binary_' + textureIndexString + GetTextureFileExtension (gltfImage.mimeType);
textureParams.url = OV.CreateObjectUrlWithMimeType (buffer, gltfImage.mimeType);
textureParams.buffer = buffer;
}
}
this.imageIndexToTextureParams[gltfImageIndex] = textureParams;
}
texture.name = textureParams.name;
texture.url = textureParams.url;
texture.buffer = textureParams.buffer;
this.gltfExtensions.ProcessTexture (gltfTextureRef, texture);
return texture;
}
@ -741,9 +763,6 @@ OV.ImporterGltf = class extends OV.ImporterBase
let hasNormals = (primitive.attributes.NORMAL !== undefined);
let hasUVs = (primitive.attributes.TEXCOORD_0 !== undefined);
let hasIndices = (primitive.indices !== undefined);
if (!hasVertices) {
return;
}
let mode = OV.GltfRenderMode.TRIANGLES;
if (primitive.mode !== undefined) {
@ -766,6 +785,8 @@ OV.ImporterGltf = class extends OV.ImporterBase
reader.EnumerateData (function (data) {
mesh.AddVertex (data);
});
} else {
return;
}
if (hasNormals) {

View File

@ -166,13 +166,15 @@ OV.ImporterObj = class extends OV.ImporterBase
);
}
function CreateTexture (keyword, line, callbacks)
function CreateTexture (importer, keyword, line)
{
let texture = new OV.TextureMap ();
texture.name = OV.NameFromLine (line, keyword.length, '#');
let textureObjectUrl = callbacks.getTextureObjectUrl (texture.name);
if (textureObjectUrl !== null) {
texture.url = textureObjectUrl;
let textureName = OV.NameFromLine (line, keyword.length, '#');
let textureBuffer = importer.GetTextureBuffer (textureName);
texture.name = textureName;
if (textureBuffer !== null) {
texture.url = textureBuffer.url;
texture.buffer = textureBuffer.buffer;
}
return texture;
}
@ -206,9 +208,9 @@ OV.ImporterObj = class extends OV.ImporterBase
return true;
}
let fileName = OV.NameFromLine (line, keyword.length, '#');
let fileContent = this.callbacks.getFileContent (fileName);
if (fileContent !== null) {
OV.ReadLines (fileContent, function (line) {
let fileBuffer = this.GetFileBuffer (fileName);
if (fileBuffer !== null) {
OV.ReadLines (fileBuffer, function (line) {
if (!obj.IsError ()) {
obj.ProcessLine (line);
}
@ -219,19 +221,19 @@ OV.ImporterObj = class extends OV.ImporterBase
if (this.currentMaterial === null || parameters.length === 0) {
return true;
}
this.currentMaterial.diffuseMap = CreateTexture (keyword, line, this.callbacks);
this.currentMaterial.diffuseMap = CreateTexture (this, keyword, line);
return true;
} else if (keyword === 'map_ks') {
if (this.currentMaterial === null || parameters.length === 0) {
return true;
}
this.currentMaterial.specularMap = CreateTexture (keyword, line, this.callbacks);
this.currentMaterial.specularMap = CreateTexture (this, keyword, line);
return true;
} else if (keyword === 'map_bump' || keyword === 'bump') {
if (this.currentMaterial === null || parameters.length === 0) {
return true;
}
this.currentMaterial.bumpMap = CreateTexture (keyword, line, this.callbacks);
this.currentMaterial.bumpMap = CreateTexture (this, keyword, line);
return true;
} else if (keyword === 'ka') {
if (this.currentMaterial === null || parameters.length < 3) {

View File

@ -24,6 +24,12 @@ OV.AsciiStringToArrayBuffer = function (str)
return buffer;
};
OV.Utf8StringToArrayBuffer = function (str)
{
let encoder = new TextEncoder ();
return encoder.encode (str);
};
OV.Base64DataURIToArrayBuffer = function (uri)
{
let dataPrefix = 'data:';
@ -54,3 +60,22 @@ OV.Base64DataURIToArrayBuffer = function (uri)
buffer : buffer
};
};
OV.CreateObjectUrl = function (content)
{
let blob = new Blob ([content]);
let url = URL.createObjectURL (blob);
return url;
};
OV.CreateObjectUrlWithMimeType = function (content, mimeType)
{
let blob = new Blob ([content], { type : mimeType });
let url = URL.createObjectURL (blob);
return url;
};
OV.RevokeObjectUrl = function (url)
{
URL.revokeObjectURL (url);
};

View File

@ -9,6 +9,22 @@ OV.MeshPrimitiveBuffer = class
this.material = null;
}
GetBounds ()
{
let min = [Infinity, Infinity, Infinity];
let max = [-Infinity, -Infinity, -Infinity];
for (let i = 0; i < this.vertices.length / 3; i++) {
for (let j = 0; j < 3; j++) {
min[j] = Math.min (min[j], this.vertices[i * 3 + j]);
max[j] = Math.max (max[j], this.vertices[i * 3 + j]);
}
}
return {
min : min,
max : max
};
}
GetByteLength (indexTypeSize, numberTypeSize)
{
let indexCount = this.indices.length;

View File

@ -54,11 +54,17 @@ OV.TextureMap = class
{
this.name = null;
this.url = null;
this.buffer = null;
this.offset = new OV.Coord2D (0.0, 0.0);
this.scale = new OV.Coord2D (1.0, 1.0);
this.rotation = 0.0; // radians
}
IsValid ()
{
return this.name !== null && this.url !== null && this.buffer !== null;
}
HasTransformation ()
{
if (!OV.CoordIsEqual2D (this.offset, new OV.Coord2D (0.0, 0.0))) {
@ -78,6 +84,7 @@ OV.TextureMap = class
let cloned = new OV.TextureMap ();
cloned.name = this.name;
cloned.url = this.url;
cloned.buffer = this.buffer;
cloned.offset = this.offset.Clone ();
cloned.scale = this.scale.Clone ();
cloned.rotation = this.rotation;
@ -110,6 +117,25 @@ OV.Material = class
this.multiplyDiffuseMap = false;
}
EnumerateTextureMaps (enumerator)
{
if (this.diffuseMap !== null) {
enumerator (this.diffuseMap);
}
if (this.specularMap !== null) {
enumerator (this.specularMap);
}
if (this.bumpMap !== null) {
enumerator (this.bumpMap);
}
if (this.normalMap !== null) {
enumerator (this.normalMap);
}
if (this.emissiveMap !== null) {
enumerator (this.emissiveMap);
}
}
Clone ()
{
let cloned = new OV.Material ();

View File

@ -42,7 +42,7 @@ OV.Init3DViewerElements = function ()
onTextureLoaded : function () {
viewer.Render ();
},
onLoadError : function (importerError) {
onLoadError : function (importError) {
progressDiv.innerHTML = 'Unknown error.';
},
});

View File

@ -1,8 +0,0 @@
{
"dependencies": {
"mocha": "*"
},
"scripts": {
"test": "mocha"
}
}

View File

@ -1,20 +1,7 @@
var fs = require ('fs');
process.chdir (__dirname);
global.atob = function (base64String) {
return Buffer.from (base64String, 'base64').toString('binary')
};
global.Blob = function () {
};
global.URL = {
createObjectURL : function () {
return 'ObjectUrl';
}
}
require ('./utils/globals.js');
require ('./utils/importall.js');
var testDirName = './tests/';
var files = fs.readdirSync (testDirName, { withFileTypes: true });

View File

@ -0,0 +1 @@
invalid 3ds file

View File

@ -0,0 +1,5 @@
newmtl Material
Ka 0.000000 0.000000 0.000000
Kd 1.000000 1.000000 1.000000
Ks 0.000000 0.000000 0.000000
map_Kd texture.png

View File

@ -0,0 +1,15 @@
mtllib rectangle_with_texture.mtl
v 0.0 0.0 0.0
v 1.0 0.0 0.0
v 1.0 1.0 0.0
v 0.0 1.0 0.0
vn 0.0 0.0 1.0
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
o Mesh
usemtl Material
f 1/1/1 2/2/1 3/3/1 4/4/1

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

View File

@ -0,0 +1,11 @@
newmtl Material1
Ka 0.000000 0.000000 0.000000
Kd 1.000000 1.000000 1.000000
Ks 0.000000 0.000000 0.000000
map_Kd ./texture.png
newmtl Material2
Ka 0.000000 0.000000 0.000000
Kd 1.000000 1.000000 1.000000
Ks 0.000000 0.000000 0.000000
map_Kd texture.png

View File

@ -0,0 +1,17 @@
mtllib two_materials_same_texture.mtl
v 0.0 0.0 0.0
v 1.0 0.0 0.0
v 1.0 1.0 0.0
v 0.0 1.0 0.0
vn 0.0 0.0 1.0
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
o Mesh
usemtl Material1
f 1/1/1 2/2/1 3/3/1
usemtl Material2
f 1/1/1 3/3/1 4/4/1

1
test/testfiles/wrong.ext Normal file
View File

@ -0,0 +1 @@
file with not importable extension

View File

@ -12,12 +12,15 @@ function CreateTestModel ()
material1.diffuseMap = new OV.TextureMap ();
material1.diffuseMap.name = 'textures/texture1.png';
material1.diffuseMap.url = 'texture1_url';
material1.diffuseMap.buffer = new ArrayBuffer (4);
material1.specularMap = new OV.TextureMap ();
material1.specularMap.name = 'textures/texture2.png';
material1.specularMap.url = 'texture2_url';
material1.specularMap.buffer = new ArrayBuffer (4);
material1.bumpMap = new OV.TextureMap ();
material1.bumpMap.name = 'textures/texture3.png';
material1.bumpMap.url = 'texture3_url';
material1.bumpMap.buffer = new ArrayBuffer (4);
model.AddMaterial (material1);
let material2 = new OV.Material ();
@ -64,55 +67,57 @@ describe ('Exporter', function () {
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let result = exporter.Export (model, OV.FileFormat.Text, 'obj', null);
let result = exporter.Export (model, OV.FileFormat.Text, 'obj');
assert.strictEqual (result.length, 5);
let mtlFile = result[0];
assert.strictEqual (mtlFile.GetName (), 'model.mtl');
assert.strictEqual (mtlFile.GetContent (),
`newmtl TestMaterial1
Ka 0 0 0
Kd 1 0 0
Ks 0.2 0.2 0.2
Ns 0
d 1
map_Kd texture1.png
map_Ks texture2.png
bump texture3.png
newmtl TestMaterial2
Ka 0 0 0
Kd 0 1 0
Ks 0.2 0.2 0.2
Ns 0
d 1
`
);
[
'newmtl TestMaterial1',
'Ka 0 0 0',
'Kd 1 0 0',
'Ks 0.2 0.2 0.2',
'Ns 0',
'd 1',
'map_Kd texture1.png',
'map_Ks texture2.png',
'bump texture3.png',
'newmtl TestMaterial2',
'Ka 0 0 0',
'Kd 0 1 0',
'Ks 0.2 0.2 0.2',
'Ns 0',
'd 1',
''
].join ('\n'));
let objFile = result[1];
assert.strictEqual (objFile.GetName (), 'model.obj');
assert.strictEqual (objFile.GetContent (),
`mtllib model.mtl
g TestMesh1
v 0 0 1
v 1 0 1
v 0 1 1
vn 0 0 1
usemtl TestMaterial1
f 1//1 2//1 3//1
g TestMesh2
v 0 0 0
v 1 0 0
v 0 1 0
v -1 0 0
v 0 0 1
vn 0 0 1
vn 0 0 1
vn -1 0 0
f 4//2 5//2 6//2
usemtl TestMaterial2
f 4//3 6//3 7//3
f 4//4 8//4 6//4
`
);
[
'mtllib model.mtl',
'g TestMesh1',
'v 0 0 1',
'v 1 0 1',
'v 0 1 1',
'vn 0 0 1',
'usemtl TestMaterial1',
'f 1//1 2//1 3//1',
'g TestMesh2',
'v 0 0 0',
'v 1 0 0',
'v 0 1 0',
'v -1 0 0',
'v 0 0 1',
'vn 0 0 1',
'vn 0 0 1',
'vn -1 0 0',
'f 4//2 5//2 6//2',
'usemtl TestMaterial2',
'f 4//3 6//3 7//3',
'f 4//4 8//4 6//4',
''
].join ('\n'));
let textureFile1 = result[2];
assert.strictEqual (textureFile1.GetName (), 'texture1.png');
@ -134,51 +139,52 @@ f 4//4 8//4 6//4
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let result = exporter.Export (model, OV.FileFormat.Text, 'stl', null);
let result = exporter.Export (model, OV.FileFormat.Text, 'stl');
assert.strictEqual (result.length, 1);
let stlFile = result[0];
assert.strictEqual (stlFile.GetName (), 'model.stl');
assert.strictEqual (stlFile.GetContent (),
`solid Model
facet normal 0 0 1
outer loop
vertex 0 0 1
vertex 1 0 1
vertex 0 1 1
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 0 0
vertex 1 0 0
vertex 0 1 0
endloop
endfacet
facet normal 0 0 1
outer loop
vertex 0 0 0
vertex 0 1 0
vertex -1 0 0
endloop
endfacet
facet normal -1 0 0
outer loop
vertex 0 0 0
vertex 0 0 1
vertex 0 1 0
endloop
endfacet
endsolid Model
`
);
});
[
'solid Model',
'facet normal 0 0 1',
' outer loop',
' vertex 0 0 1',
' vertex 1 0 1',
' vertex 0 1 1',
' endloop',
'endfacet',
'facet normal 0 0 1',
' outer loop',
' vertex 0 0 0',
' vertex 1 0 0',
' vertex 0 1 0',
' endloop',
'endfacet',
'facet normal 0 0 1',
' outer loop',
' vertex 0 0 0',
' vertex 0 1 0',
' vertex -1 0 0',
' endloop',
'endfacet',
'facet normal -1 0 0',
' outer loop',
' vertex 0 0 0',
' vertex 0 0 1',
' vertex 0 1 0',
' endloop',
'endfacet',
'endsolid Model',
''
].join ('\n'));
});
it ('Stl Binary Export', function () {
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let result = exporter.Export (model, OV.FileFormat.Binary, 'stl', null);
let result = exporter.Export (model, OV.FileFormat.Binary, 'stl');
assert.strictEqual (result.length, 1);
let stlFile = result[0];
@ -195,76 +201,78 @@ endsolid Model
let importedModel = importer.GetModel ();
assert.strictEqual (importedModel.VertexCount (), 12);
assert.strictEqual (importedModel.TriangleCount (), 4);
});
});
it ('Off Export', function () {
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let result = exporter.Export (model, OV.FileFormat.Text, 'off', null);
assert.strictEqual (result.length, 1);
let result = exporter.Export (model, OV.FileFormat.Text, 'off');
assert.strictEqual (result.length, 1);
let offFile = result[0];
assert.strictEqual (offFile.GetName (), 'model.off');
assert.strictEqual (offFile.GetContent (),
`OFF
8 4 0
0 0 1
1 0 1
0 1 1
0 0 0
1 0 0
0 1 0
-1 0 0
0 0 1
3 0 1 2
3 3 4 5
3 3 5 6
3 3 7 5
`
);
let offFile = result[0];
assert.strictEqual (offFile.GetName (), 'model.off');
assert.strictEqual (offFile.GetContent (),
[
'OFF',
'8 4 0',
'0 0 1',
'1 0 1',
'0 1 1',
'0 0 0',
'1 0 0',
'0 1 0',
'-1 0 0',
'0 0 1',
'3 0 1 2',
'3 3 4 5',
'3 3 5 6',
'3 3 7 5',
''
].join ('\n'));
});
it ('Ply Export', function () {
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let result = exporter.Export (model, OV.FileFormat.Text, 'ply', null);
assert.strictEqual (result.length, 1);
let result = exporter.Export (model, OV.FileFormat.Text, 'ply');
assert.strictEqual (result.length, 1);
let plyFile = result[0];
assert.strictEqual (plyFile.GetName (), 'model.ply');
assert.strictEqual (plyFile.GetContent (),
`ply
format ascii 1.0
element vertex 8
property float x
property float y
property float z
element face 4
property list uchar int vertex_index
end_header
0 0 1
1 0 1
0 1 1
0 0 0
1 0 0
0 1 0
-1 0 0
0 0 1
3 0 1 2
3 3 4 5
3 3 5 6
3 3 7 5
`
);
let plyFile = result[0];
assert.strictEqual (plyFile.GetName (), 'model.ply');
assert.strictEqual (plyFile.GetContent (),
[
'ply',
'format ascii 1.0',
'element vertex 8',
'property float x',
'property float y',
'property float z',
'element face 4',
'property list uchar int vertex_index',
'end_header',
'0 0 1',
'1 0 1',
'0 1 1',
'0 0 0',
'1 0 0',
'0 1 0',
'-1 0 0',
'0 0 1',
'3 0 1 2',
'3 3 4 5',
'3 3 5 6',
'3 3 7 5',
''
].join ('\n'));
});
it ('Ply Binary Export', function () {
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let result = exporter.Export (model, OV.FileFormat.Binary, 'ply', null);
let result = exporter.Export (model, OV.FileFormat.Binary, 'ply');
assert.strictEqual (result.length, 1);
let plyFile = result[0];
@ -284,55 +292,51 @@ end_header
});
it ('Gltf Ascii Export', function () {
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let result = exporter.Export (model, OV.FileFormat.Text, 'gltf', null);
assert.strictEqual (result.length, 3);
let gltfFile = result[0];
let binFile = result[1];
let textureFile = result[2];
assert.strictEqual (gltfFile.GetName (), 'model.gltf');
assert.strictEqual (binFile.GetName (), 'model.bin');
assert.strictEqual (textureFile.GetName (), 'texture1.png');
assert.strictEqual (textureFile.GetUrl (), 'texture1_url');
assert.strictEqual (textureFile.GetContent (), null);
let contentBuffer = gltfFile.GetContent ();
let importer = new OV.ImporterGltf ();
importer.Import (contentBuffer, 'gltf', {
getDefaultMaterial () {
return new OV.Material ();
},
getFileContent (filePath) {
return binFile.GetContent ();
},
getTextureObjectUrl (filePath) {
return null;
}
});
let importedModel = importer.GetModel ();
assert (OV.CheckModel (importedModel));
assert.strictEqual (importedModel.MaterialCount (), 2);
assert.strictEqual (importedModel.MeshCount (), 2);
assert.strictEqual (importedModel.GetMesh (0).GetName (), 'TestMesh1');
assert.strictEqual (importedModel.GetMesh (1).GetName (), 'TestMesh2');
assert.strictEqual (importedModel.VertexCount (), 12);
assert.strictEqual (importedModel.TriangleCount (), 4);
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let result = exporter.Export (model, OV.FileFormat.Text, 'gltf');
assert.strictEqual (result.length, 3);
let gltfFile = result[0];
let binFile = result[1];
let textureFile = result[2];
assert.strictEqual (gltfFile.GetName (), 'model.gltf');
assert.strictEqual (binFile.GetName (), 'model.bin');
assert.strictEqual (textureFile.GetName (), 'texture1.png');
assert.strictEqual (textureFile.GetUrl (), 'texture1_url');
assert.strictEqual (textureFile.GetContent (), null);
let contentBuffer = gltfFile.GetContent ();
let importer = new OV.ImporterGltf ();
importer.Import (contentBuffer, 'gltf', {
getDefaultMaterial () {
return new OV.Material ();
},
getFileBuffer (filePath) {
if (filePath == 'model.bin') {
return binFile.GetContent ();
}
return null;
}
});
let importedModel = importer.GetModel ();
assert (OV.CheckModel (importedModel));
assert.strictEqual (importedModel.MaterialCount (), 2);
assert.strictEqual (importedModel.MeshCount (), 2);
assert.strictEqual (importedModel.GetMesh (0).GetName (), 'TestMesh1');
assert.strictEqual (importedModel.GetMesh (1).GetName (), 'TestMesh2');
assert.strictEqual (importedModel.VertexCount (), 12);
assert.strictEqual (importedModel.TriangleCount (), 4);
});
it ('Gltf Binary Export', function () {
let model = CreateTestModel ();
let exporter = new OV.Exporter ();
let result = exporter.Export (model, OV.FileFormat.Binary, 'glb', {
getTextureBuffer : function (name) {
return new ArrayBuffer (4);
}
});
let result = exporter.Export (model, OV.FileFormat.Binary, 'glb');
assert.strictEqual (result.length, 1);
let glbFile = result[0];
@ -344,10 +348,7 @@ end_header
getDefaultMaterial () {
return new OV.Material ();
},
getFileContent (filePath) {
return binFile.GetContent ();
},
getTextureObjectUrl (filePath) {
getFileBuffer (filePath) {
return null;
}
});

182
test/tests/importer_test.js Normal file
View File

@ -0,0 +1,182 @@
var assert = require ('assert');
var path = require ('path');
function ImportFilesWithImporter (importer, files, callbacks)
{
importer.LoadFilesFromFileObjects (files, function () {
importer.Import ({
success : function (importResult) {
callbacks.success (importer, importResult);
},
error : function (importError) {
callbacks.error (importer, importError);
}
});
});
}
function ImportFiles (files, callbacks)
{
let importer = new OV.Importer ();
ImportFilesWithImporter (importer, files, callbacks);
}
describe ('Importer Test', function () {
it ('Empty File List', function () {
let files = [];
ImportFiles (files, {
success : function (importer, importResult) {
assert.fail ();
},
error : function (importer, importError) {
assert.strictEqual (importError.code, OV.ImportErrorCode.NoImportableFile);
}
});
});
it ('Not existing file', function () {
let files = [
new FileObject ('obj', 'missing.obj')
];
ImportFiles (files, {
success : function (importer, importResult) {
assert.fail ();
},
error : function (importer, importError) {
assert.strictEqual (importError.code, OV.ImportErrorCode.NoImportableFile);
}
});
});
it ('Not imprtable file', function () {
let files = [
new FileObject ('', 'wrong.ext')
];
ImportFiles (files, {
success : function (importer, importResult) {
assert.fail ();
},
error : function (importer, importError) {
assert.strictEqual (importError.code, OV.ImportErrorCode.NoImportableFile);
}
});
});
it ('Wrong file', function () {
let files = [
new FileObject ('3ds', 'wrong.3ds')
];
ImportFiles (files, {
success : function (importer, importResult) {
assert.fail ();
},
error : function (importer, importError) {
assert.strictEqual (importError.code, OV.ImportErrorCode.ImportFailed);
}
});
});
it ('Single file', function () {
let files = [
new FileObject ('obj', 'single_triangle.obj')
];
ImportFiles (files, {
success : function (importer, importResult) {
assert (!OV.IsModelEmpty (importResult.model));
assert.deepStrictEqual (importResult.usedFiles, ['single_triangle.obj']);
assert.deepStrictEqual (importResult.missingFiles, []);
},
error : function (importer, importError) {
assert.fail ();
}
});
});
it ('Missing files', function () {
let files = [];
files.push (new FileObject ('obj', 'cube_with_materials.obj'));
ImportFiles (files, {
success : function (importer, importResult) {
assert (!OV.IsModelEmpty (importResult.model));
assert.deepStrictEqual (importResult.usedFiles, ['cube_with_materials.obj']);
assert.deepStrictEqual (importResult.missingFiles, ['cube_with_materials.mtl']);
},
error : function (importer, importError) {
assert.fail ();
}
});
files.push (new FileObject ('obj', 'cube_with_materials.mtl'));
ImportFiles (files, {
success : function (importer, importResult) {
assert (!OV.IsModelEmpty (importResult.model));
assert.deepStrictEqual (importResult.usedFiles, ['cube_with_materials.obj', 'cube_with_materials.mtl']);
assert.deepStrictEqual (importResult.missingFiles, ['cube_texture.png']);
},
error : function (importer, importError) {
assert.fail ();
}
});
files.push (new FileObject ('obj', 'cube_texture.png'));
ImportFiles (files, {
success : function (importer, importResult) {
assert (!OV.IsModelEmpty (importResult.model));
assert.deepStrictEqual (importResult.usedFiles, ['cube_with_materials.obj', 'cube_with_materials.mtl', 'cube_texture.png']);
assert.deepStrictEqual (importResult.missingFiles, []);
},
error : function (importer, importError) {
assert.fail ();
}
});
});
it ('Missing texture multiple times', function () {
let files = [
new FileObject ('obj', 'two_materials_same_texture.obj'),
new FileObject ('obj', 'two_materials_same_texture.mtl'),
];
ImportFiles (files, {
success : function (importer, importResult) {
assert (!OV.IsModelEmpty (importResult.model));
assert.deepStrictEqual (importResult.usedFiles, ['two_materials_same_texture.obj', 'two_materials_same_texture.mtl']);
assert.deepStrictEqual (importResult.missingFiles, ['texture.png']);
},
error : function (importer, importError) {
assert.fail ();
}
});
});
it ('Append Missing files', function () {
let theImporter = new OV.Importer ();
ImportFilesWithImporter (theImporter, [new FileObject ('obj', 'cube_with_materials.obj')], {
success : function (importer, importResult) {
assert (!OV.IsModelEmpty (importResult.model));
assert.deepStrictEqual (importResult.usedFiles, ['cube_with_materials.obj']);
assert.deepStrictEqual (importResult.missingFiles, ['cube_with_materials.mtl']);
ImportFilesWithImporter (theImporter, [new FileObject ('obj', 'cube_with_materials.mtl')], {
success : function (importer, importResult) {
assert (!OV.IsModelEmpty (importResult.model));
assert.deepStrictEqual (importResult.usedFiles, ['cube_with_materials.obj', 'cube_with_materials.mtl']);
assert.deepStrictEqual (importResult.missingFiles, ['cube_texture.png']);
ImportFilesWithImporter (theImporter, [new FileObject ('obj', 'cube_texture.png')], {
success : function (importer, importResult) {
assert (!OV.IsModelEmpty (importResult.model));
assert.deepStrictEqual (importResult.usedFiles, ['cube_with_materials.obj', 'cube_with_materials.mtl', 'cube_texture.png']);
assert.deepStrictEqual (importResult.missingFiles, []);
},
error : function (importer, importError) {
assert.fail ();
}
});
},
error : function (importer, importError) {
assert.fail ();
}
});
},
error : function (importer, importError) {
assert.fail ();
}
});
});
});

View File

@ -374,6 +374,69 @@ describe ('Obj Importer', function() {
});
});
it ('rectangle_with_texture.obj', function () {
var model = testFiles.ImportObjFile ('rectangle_with_texture.obj');
assert (OV.CheckModel (model));
assert.deepStrictEqual (testUtils.ModelToObject (model), {
name : '',
materials : [
{ name : 'Material' }
],
meshes : [
{
name : 'Mesh',
triangles : [
{
vertices : [0, 0, 0, 1, 0, 0, 1, 1, 0],
normals : [0, 0, 1, 0, 0, 1, 0, 0, 1],
uvs : [0, 0, 1, 0, 1, 1],
mat : 0
},
{
vertices : [0, 0, 0, 1, 1, 0, 0, 1, 0],
normals : [0, 0, 1, 0, 0, 1, 0, 0, 1],
uvs : [0, 0, 1, 1, 0, 1],
mat : 0
}
]
}
]
});
});
it ('two_materials_same_texture.obj', function () {
var model = testFiles.ImportObjFile ('two_materials_same_texture.obj');
assert.strictEqual (model.GetMaterial (0).diffuseMap.url, model.GetMaterial (1).diffuseMap.url);
assert.strictEqual (model.GetMaterial (0).diffuseMap.buffer.byteLength, model.GetMaterial (1).diffuseMap.buffer.byteLength);
assert (OV.CheckModel (model));
assert.deepStrictEqual (testUtils.ModelToObject (model), {
name : '',
materials : [
{ name : 'Material1' },
{ name : 'Material2' }
],
meshes : [
{
name : 'Mesh',
triangles : [
{
vertices : [0, 0, 0, 1, 0, 0, 1, 1, 0],
normals : [0, 0, 1, 0, 0, 1, 0, 0, 1],
uvs : [0, 0, 1, 0, 1, 1],
mat : 0
},
{
vertices : [0, 0, 0, 1, 1, 0, 0, 1, 0],
normals : [0, 0, 1, 0, 0, 1, 0, 0, 1],
uvs : [0, 0, 1, 1, 0, 1],
mat : 1
}
]
}
]
});
});
it ('cube_with_materials.obj', function () {
var model = testFiles.ImportObjFile ('cube_with_materials.obj');
assert (OV.CheckModel (model));

View File

@ -1,9 +1,9 @@
var assert = require ('assert');
var testFiles = require ('../utils/testfiles.js');
var testUtils = require ('../utils/testutils.js');
describe ('IO Test', function () {
it ('Binary Reader', function () {
let buffer = testFiles.GetArrayBufferFileContent ('bin', 'binary_content.bin');
let buffer = testUtils.GetArrayBufferFileContent ('bin', 'binary_content.bin');
let reader = new OV.BinaryReader (buffer, true);
assert.strictEqual (reader.GetByteLength (), 166);
@ -89,4 +89,13 @@ describe ('IO Test', function () {
assert.strictEqual (reader.ReadFloat32 (), 7.5);
assert.strictEqual (reader.ReadDouble64 (), 8.5);
});
it ('Utf8 Conversion', function () {
let str = 'example-\u2764-example';
let buffer = OV.Utf8StringToArrayBuffer (str);
assert.strictEqual (buffer.byteLength, 19);
let str2 = OV.ArrayBufferToUtf8String (buffer);
assert.strictEqual (str, str2);
assert.strictEqual (str.length, str2.length);
});
});

60
test/utils/globals.js Normal file
View File

@ -0,0 +1,60 @@
var path = require ('path');
var testUtils = require ('./testutils.js');
global.atob = function (base64String) {
return Buffer.from (base64String, 'base64').toString('binary');
};
global.Blob = function () {
};
var objectUrlCounter = 0;
global.URL = {
createObjectURL : function () {
objectUrlCounter += 1;
return 'ObjectUrl:' + objectUrlCounter.toString ();
}
};
global.FileObject = function (folderName, fileName)
{
this.folderName = folderName;
this.fileName = fileName;
this.name = path.join (folderName, fileName);
};
global.FileReader = class
{
static DONE = 2;
readAsText (fileObject)
{
let content = testUtils.GetTextFileContent (fileObject.folderName, fileObject.fileName);
if (content !== null) {
this.onloadend ({
target : {
readyState : FileReader.DONE,
result : content
}
});
} else {
this.onerror ();
}
}
readAsArrayBuffer (fileObject)
{
let content = testUtils.GetArrayBufferFileContent (fileObject.folderName, fileObject.fileName);
if (content !== null) {
this.onloadend ({
target : {
readyState : FileReader.DONE,
result : content
}
});
} else {
this.onerror ();
}
}
};

View File

@ -1,28 +1,7 @@
var path = require ('path');
var fs = require ('fs');
var testUtils = require ('./testutils.js')
var testUtils = require ('./testutils.js');
module.exports =
{
GetTextFileContent : function (folder, fileName)
{
var testFilePath = path.join ('testfiles', folder, fileName);
return fs.readFileSync (testFilePath).toString ();
},
GetArrayBufferFileContent : function (folder, fileName)
{
var testFilePath = path.join ('testfiles', folder, fileName);
var buffer = fs.readFileSync (testFilePath);
var arrayBuffer = new ArrayBuffer (buffer.length);
var uint8Array = new Uint8Array (arrayBuffer);
var i;
for (i = 0; i < buffer.length; ++i) {
uint8Array[i] = buffer[i];
}
return arrayBuffer
},
ImportObjFile : function (fileName)
{
var importer = new OV.ImporterObj ();
@ -65,13 +44,30 @@ module.exports =
},
ImportFile : function (importer, format, folder, fileName)
{
var myThis = this;
{
function GetFileBuffer (filePath, importer)
{
let extension = OV.GetFileExtension (filePath);
let knownFormats = importer.GetKnownFileFormats ();
let format = OV.FileFormat.Binary;
if (knownFormats[extension] !== undefined) {
format = knownFormats[extension];
}
let fileContent = null;
if (format == OV.FileFormat.Text) {
fileContent = testUtils.GetTextFileContent (folder, filePath);
} else if (format == OV.FileFormat.Binary) {
fileContent = testUtils.GetArrayBufferFileContent (folder, filePath);
}
return fileContent;
}
var obj = this;
var content = null;
if (format == OV.FileFormat.Text) {
content = this.GetTextFileContent (folder, fileName);
content = testUtils.GetTextFileContent (folder, fileName);
} else if (format == OV.FileFormat.Binary) {
content = this.GetArrayBufferFileContent (folder, fileName);
content = testUtils.GetArrayBufferFileContent (folder, fileName);
}
var extension = OV.GetFileExtension (fileName);
importer.Import (content, extension, {
@ -79,23 +75,8 @@ module.exports =
var material = new OV.Material ();
return material;
},
getFileContent : function (requestedFileName) {
let extension = OV.GetFileExtension (requestedFileName);
let knownFormats = importer.GetKnownFileFormats ();
let format = OV.FileFormat.Binary;
if (knownFormats[extension] !== undefined) {
format = knownFormats[extension];
}
let fileContent = null;
if (format == OV.FileFormat.Text) {
fileContent = myThis.GetTextFileContent (folder, requestedFileName);
} else if (format == OV.FileFormat.Binary) {
fileContent = myThis.GetArrayBufferFileContent (folder, requestedFileName);
}
return fileContent;
},
getTextureObjectUrl : function (requestedFileName) {
return requestedFileName;
getFileBuffer : function (filePath) {
return GetFileBuffer (filePath, importer);
}
});
let model = importer.GetModel ();

View File

@ -1,5 +1,30 @@
var fs = require ('fs');
var path = require ('path');
module.exports =
{
GetTextFileContent : function (folder, fileName)
{
var testFilePath = path.join ('testfiles', folder, fileName);
if (!fs.existsSync (testFilePath)) {
return null;
}
return fs.readFileSync (testFilePath).toString ();
},
GetArrayBufferFileContent : function (folder, fileName)
{
var testFilePath = path.join ('testfiles', folder, fileName);
var buffer = fs.readFileSync (testFilePath);
var arrayBuffer = new ArrayBuffer (buffer.length);
var uint8Array = new Uint8Array (arrayBuffer);
var i;
for (i = 0; i < buffer.length; ++i) {
uint8Array[i] = buffer[i];
}
return arrayBuffer
},
ModelToObject : function (model)
{
var obj = {

View File

@ -37,7 +37,7 @@ def CompressFiles (inputFiles, outputFile):
return False
return True
def CreateDestinationDir (config, rootDir, websiteDir, version):
def CreateDestinationDir (config, rootDir, websiteDir, version, testBuild):
if not os.path.exists (websiteDir):
os.makedirs (websiteDir)
@ -71,7 +71,7 @@ def CreateDestinationDir (config, rootDir, websiteDir, version):
metaContent = Tools.GetFileContent (metaFile)
replacer.ReplaceTokenContent ('<!-- meta start -->', '<!-- meta end -->', metaContent)
analyticsFile = os.path.join (rootDir, 'tools', 'website_analytics_data.txt')
if os.path.exists (analyticsFile):
if os.path.exists (analyticsFile) and not testBuild:
analyticsContent = Tools.GetFileContent (analyticsFile)
replacer.ReplaceTokenContent ('<!-- analytics start -->', '<!-- analytics end -->', analyticsContent)
scriptFile = os.path.join (rootDir, 'tools', 'website_script_data.txt')
@ -86,8 +86,8 @@ def CreatePackage (websiteDir, packageDir, version):
zipPath = os.path.join (packageDir, 'o3dv_' + version + '.zip')
zip = zipfile.ZipFile (zipPath, mode = 'w', compression = zipfile.ZIP_DEFLATED)
zip.write (os.path.join (websiteDir, 'libs', 'three.min-126.js'), 'three.min.js')
zip.write (os.path.join (websiteDir, 'o3dv', 'o3dv.min.js'), 'o3dv.min.js')
zip.write (os.path.join (websiteDir, 'libs', 'three.min-126.js'), 'three.min-126.js')
zip.write (os.path.join (websiteDir, 'o3dv', 'o3dv.min.js'), 'o3dv.min-' + version + '.js')
zip.close ()
return True
@ -96,12 +96,19 @@ def Main (argv):
os.chdir (currentDir)
rootDir = os.path.abspath ('..')
buildDir = os.path.join (rootDir, 'build')
testBuild = False
buildDir = os.path.join (rootDir, 'build', 'final')
if len (argv) >= 2 and argv[1] == 'test':
testBuild = True
buildDir = os.path.join (rootDir, 'build', 'test')
PrintInfo ('Creating test build.')
websiteDir = os.path.join (buildDir, 'website')
packageDir = os.path.join (buildDir, 'package')
if os.path.exists (buildDir):
shutil.rmtree (buildDir)
config = None
with open ('config.json') as configJson:
config = json.load (configJson)
@ -120,7 +127,7 @@ def Main (argv):
version = GetVersion (rootDir)
PrintInfo ('Create build directory')
CreateDestinationDir (config, rootDir, websiteDir, version)
CreateDestinationDir (config, rootDir, websiteDir, version, testBuild)
PrintInfo ('Compress importer sources.')
compressResult = CompressFiles (config['importer_files'], os.path.join (websiteDir, 'o3dv', 'o3dv.min.js'))

View File

@ -11,22 +11,22 @@
<body>
<iframe
src="embed.html#model=../test/testfiles/3ds/cube_four_instances.3ds,../test/testfiles/3ds/texture.png"
src="../../website/embed.html#model=../test/testfiles/3ds/cube_four_instances.3ds,../test/testfiles/3ds/texture.png"
width=400 height=300
style="border:1px solid #eeeeee;">
</iframe>
<iframe
src="embed.html#model=../test/testfiles/3ds/cube_four_instances.3ds,../test/testfiles/3ds/texture.png$camera=3.929421317669367,6.153966358575169,2.7076091223424714,1.5,1.5,0.5,0,0,1"
src="../../website/embed.html#model=../test/testfiles/3ds/cube_four_instances.3ds,../test/testfiles/3ds/texture.png$camera=3.929421317669367,6.153966358575169,2.7076091223424714,1.5,1.5,0.5,0,0,1"
width=400 height=300
style="border:1px solid #eeeeee;">
</iframe>
<iframe
src="embed.html#model=../test/testfiles/obj/hundred_cubes.obj,../test/testfiles/obj/hundred_cubes.mtl"
src="../../website/embed.html#model=../test/testfiles/obj/hundred_cubes.obj,../test/testfiles/obj/hundred_cubes.mtl"
width=400 height=300
style="border:1px solid #eeeeee;">
</iframe>
<iframe
src="embed.html#model=wrong.3ds"
src="../../website/embed.html#model=wrong.3ds"
width=400 height=300
style="border:1px solid #eeeeee;">
</iframe>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="icon" type="image/png" href="assets/images/3dviewer_net_favicon.png">
<title>Online 3D Viewer</title>
<script type="text/javascript" src="../../libs/three.min-126.js"></script>
<script type="text/javascript" src="../../build/final/website/o3dv/o3dv.min.js"></script>
<script type='text/javascript'>
OV.Init3DViewerElements ();
</script>
<style>
div.online_3d_viewer
{
float: left;
}
</style>
</head>
<body>
<div class="online_3d_viewer" width="400" height="300" model="../../test/testfiles/3ds/cube_four_instances.3ds,../../test/testfiles/3ds/texture.png"></div>
<div class="online_3d_viewer"
width="400" height="300"
model="../../test/testfiles/3ds/cube_four_instances.3ds,../../test/testfiles/3ds/texture.png"
camera="-1.5,-3.0,2.0,0,0,0,0,0,1">
</div>
<div class="online_3d_viewer" width="400" height="300" model="../../test/testfiles/obj/hundred_cubes.obj,../../test/testfiles/obj/hundred_cubes.mtl"></div>
<div class="online_3d_viewer" width="400" height="300" model="wrong.3ds"></div>
</body>
</html>

View File

@ -11,7 +11,7 @@
<script type='text/javascript'>
function Sandbox3D ()
{
let canvas = document.getElementById ('canvas')
let canvas = document.getElementById ('canvas');
let parameters = {
canvas : canvas,

View File

@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="icon" type="image/png" href="assets/images/3dviewer_net_favicon.png">
<title>Online 3D Viewer</title>
<title>Online 3D Viewer Embedded</title>
<!-- meta start -->
<!-- meta end -->

View File

@ -1,80 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<link rel="icon" type="image/png" href="assets/images/3dviewer_net_favicon.png">
<title>Online 3D Viewer</title>
<!-- libs start -->
<script type="text/javascript" src="../libs/jquery-3.5.1.min.js"></script>
<script type="text/javascript" src="../libs/three.min-126.js"></script>
<!-- libs end -->
<!-- importer start -->
<script type="text/javascript" src="../source/core/core.js"></script>
<script type="text/javascript" src="../source/core/taskrunner.js"></script>
<script type="text/javascript" src="../source/geometry/geometry.js"></script>
<script type="text/javascript" src="../source/geometry/coord2d.js"></script>
<script type="text/javascript" src="../source/geometry/coord3d.js"></script>
<script type="text/javascript" src="../source/geometry/matrix.js"></script>
<script type="text/javascript" src="../source/geometry/transformation.js"></script>
<script type="text/javascript" src="../source/geometry/tween.js"></script>
<script type="text/javascript" src="../source/io/binaryreader.js"></script>
<script type="text/javascript" src="../source/io/binarywriter.js"></script>
<script type="text/javascript" src="../source/io/textwriter.js"></script>
<script type="text/javascript" src="../source/io/bufferutils.js"></script>
<script type="text/javascript" src="../source/io/fileutils.js"></script>
<script type="text/javascript" src="../source/model/modelentities.js"></script>
<script type="text/javascript" src="../source/model/mesh.js"></script>
<script type="text/javascript" src="../source/model/meshbuffer.js"></script>
<script type="text/javascript" src="../source/model/model.js"></script>
<script type="text/javascript" src="../source/model/modelutils.js"></script>
<script type="text/javascript" src="../source/model/modelfinalization.js"></script>
<script type="text/javascript" src="../source/import/importerutils.js"></script>
<script type="text/javascript" src="../source/import/importerbase.js"></script>
<script type="text/javascript" src="../source/import/importerobj.js"></script>
<script type="text/javascript" src="../source/import/importerstl.js"></script>
<script type="text/javascript" src="../source/import/importeroff.js"></script>
<script type="text/javascript" src="../source/import/importerply.js"></script>
<script type="text/javascript" src="../source/import/importer3ds.js"></script>
<script type="text/javascript" src="../source/import/importergltf.js"></script>
<script type="text/javascript" src="../source/import/importer.js"></script>
<script type="text/javascript" src="../source/export/exporterutils.js"></script>
<script type="text/javascript" src="../source/export/exporterbase.js"></script>
<script type="text/javascript" src="../source/export/exporterobj.js"></script>
<script type="text/javascript" src="../source/export/exporterstl.js"></script>
<script type="text/javascript" src="../source/export/exporterply.js"></script>
<script type="text/javascript" src="../source/export/exporteroff.js"></script>
<script type="text/javascript" src="../source/export/exportergltf.js"></script>
<script type="text/javascript" src="../source/export/exporter.js"></script>
<script type="text/javascript" src="../source/external/three.converter.js"></script>
<script type="text/javascript" src="../source/external/three.model.loader.js"></script>
<script type="text/javascript" src="../source/viewer/domutils.js"></script>
<script type="text/javascript" src="../source/viewer/navigation.js"></script>
<script type="text/javascript" src="../source/viewer/viewer.js"></script>
<script type="text/javascript" src="../source/viewer/domviewer.js"></script>
<!-- importer end -->
<script type='text/javascript'>
OV.Init3DViewerElements ();
</script>
<style>
div.online_3d_viewer
{
float: left;
}
</style>
</head>
<body>
<div class="online_3d_viewer" width="400" height="300" model="../test/testfiles/3ds/cube_four_instances.3ds,../test/testfiles/3ds/texture.png"></div>
<div class="online_3d_viewer" width="400" height="300" model="../test/testfiles/3ds/cube_four_instances.3ds,../test/testfiles/3ds/texture.png" camera="3.929421317669367,6.153966358575169,2.7076091223424714,1.5,1.5,0.5,0,0,1"></div>
<div class="online_3d_viewer" width="400" height="300" model="../test/testfiles/obj/hundred_cubes.obj,../test/testfiles/obj/hundred_cubes.mtl"></div>
<div class="online_3d_viewer" width="400" height="300" model="wrong.3ds"></div>
</body>
</html>

View File

@ -47,8 +47,8 @@
<h2 id="supported_file_formats">Supported file formats</h2>
<p>
The website supports several file formats for import and export. If a file format has an ascii and binary version,
usually it's recommended to use the binary version. During export an ascii file is created.
The website supports several file formats for import and export. If a file format has text and binary version,
usually it's recommended to use the binary version.
</p>
<p>
<table>
@ -60,7 +60,7 @@
</tr>
<tr>
<td>obj</td>
<td>ascii</td>
<td>text</td>
<td class="center green">&#x2713</td>
<td class="center green">&#x2713</td>
<td>with mtl and texture support</td>
@ -74,7 +74,7 @@
</tr>
<tr>
<td rowspan="2">stl</td>
<td>ascii</td>
<td>text</td>
<td class="center green">&#x2713</td>
<td class="center green">&#x2713</td>
<td>binary version is preferred</td>
@ -87,7 +87,7 @@
</tr>
<tr>
<td rowspan="2">ply</td>
<td>ascii</td>
<td>text</td>
<td class="center green">&#x2713</td>
<td class="center green">&#x2713</td>
<td>binary version is preferred</td>
@ -100,20 +100,20 @@
</tr>
<tr>
<td rowspan="2">gltf</td>
<td>ascii (.gltf)</td>
<td>text (.gltf)</td>
<td class="center green">&#x2713</td>
<td class="center green">&#x2713</td>
<td class="center red">&#x2717</td>
<td>version 2.0</td>
</tr>
<tr>
<td>binary (.glb)</td>
<td class="center green">&#x2713</td>
<td class="center red">&#x2717</td>
<td class="center green">&#x2713</td>
<td>version 2.0</td>
</tr>
<tr>
<td rowspan="2">off</td>
<td>ascii</td>
<td>text</td>
<td class="center green">&#x2713</td>
<td class="center green">&#x2713</td>
<td>-</td>

View File

@ -118,13 +118,13 @@ table td.center
table td.green
{
color: #21C23C;
color: #21c23c;
font-weight: bold;
}
table td.red
{
color: #BF233D;
color: #bf233d;
font-weight: bold;
}
@ -141,6 +141,8 @@ div.code
font-size: 14px;
margin: 10px 0px;
padding : 5px;
overflow: auto;
white-space: nowrap;
}
@media only screen and (max-width: 1000px)

View File

@ -54,7 +54,7 @@ OV.ButtonDialog = class
let contentDiv = this.modal.GetContentDiv ();
contentDiv.addClass ('ov_dialog');
let titleDiv = $('<div>').addClass ('ov_dialog_title').html (title).appendTo (contentDiv);
$('<div>').addClass ('ov_dialog_title').html (title).appendTo (contentDiv);
let dialogContentDiv = $('<div>').addClass ('ov_dialog_content').appendTo (contentDiv);
let buttonsDiv = $('<div>').addClass ('ov_dialog_buttons').appendTo (contentDiv);
let buttonsInnerDiv = $('<div>').addClass ('ov_dialog_buttons_inner').appendTo (buttonsDiv);
@ -65,6 +65,11 @@ OV.ButtonDialog = class
return dialogContentDiv;
}
SetCloseHandler (closeHandler)
{
this.modal.SetCloseHandler (closeHandler);
}
Show ()
{
this.modal.Open ();
@ -152,14 +157,14 @@ OV.ShowOpenUrlDialog = function (onOk)
{
for (let i = 0; i < urls.length; i++) {
let url = urls[i];
if (url.search ('www.dropbox.com') !== -1) {
if (url.search (/www\.dropbox\.com/u) !== -1) {
url = url.replace ('www.dropbox.com', 'dl.dropbox.com');
let separatorPos = url.indexOf ('?');
if (separatorPos !== -1) {
url = url.substr (0, separatorPos);
}
urls[i] = url;
} else if (url.search ('github.com') !== -1) {
} else if (url.search (/github\.com/u) !== -1) {
url = url.replace ('github.com', 'raw.githubusercontent.com');
url = url.replace ('/blob', '');
let separatorPos = url.indexOf ('?');
@ -201,7 +206,7 @@ OV.ShowOpenUrlDialog = function (onOk)
return dialog;
};
OV.ShowExportDialog = function (model, importer)
OV.ShowExportDialog = function (model)
{
if (model === null) {
return OV.ShowMessageDialog ('Export Failed', 'Please load a model to export', null);
@ -221,12 +226,14 @@ OV.ShowExportDialog = function (model, importer)
$('<div>').html (text).addClass ('ov_dialog_section').appendTo (contentDiv);
let formats = [
{ name : 'obj (ascii)', format : OV.FileFormat.Text, extension : 'obj' },
{ name : 'stl (ascii)', format : OV.FileFormat.Text, extension : 'stl' },
{ name : 'obj (text)', format : OV.FileFormat.Text, extension : 'obj' },
{ name : 'stl (text)', format : OV.FileFormat.Text, extension : 'stl' },
{ name : 'stl (binary)', format : OV.FileFormat.Binary, extension : 'stl' },
{ name : 'ply (ascii)', format : OV.FileFormat.Text, extension : 'ply' },
{ name : 'ply (text)', format : OV.FileFormat.Text, extension : 'ply' },
{ name : 'ply (binary)', format : OV.FileFormat.Binary, extension : 'ply' },
{ name : 'off (ascii)', format : OV.FileFormat.Text, extension : 'off' }
{ name : 'gltf (text)', format : OV.FileFormat.Text, extension : 'gltf' },
{ name : 'gltf (binary)', format : OV.FileFormat.Binary, extension : 'glb' },
{ name : 'off (text)', format : OV.FileFormat.Text, extension : 'off' }
];
let formatSelect = $('<select>').addClass ('ov_dialog_select').appendTo (contentDiv);
@ -239,6 +246,7 @@ OV.ShowExportDialog = function (model, importer)
let fileListSection = $('<div>').addClass ('ov_dialog_section').appendTo (contentDiv);
let fileList = $('<div>').addClass ('ov_dialog_file_list').addClass ('ov_thin_scrollbar').appendTo (fileListSection);
let createdUrls = [];
formatSelect.change (function () {
fileList.empty ();
let selectedIndex = formatSelect.prop ('selectedIndex');
@ -252,14 +260,14 @@ OV.ShowExportDialog = function (model, importer)
taskRunner.Run (1, {
runTask : function (index, ready) {
let exporter = new OV.Exporter ();
let files = exporter.Export (model, selectedFormat.format, selectedFormat.extension, null);
let files = exporter.Export (model, selectedFormat.format, selectedFormat.extension);
fileList.empty ();
for (let i = 0; i < files.length; i++) {
let file = files[i];
let url = file.GetUrl ();
if (url === null) {
let fileBlob = new Blob ([file.GetContent ()]);
url = URL.createObjectURL (fileBlob);
url = OV.CreateObjectUrl (file.GetContent ());
createdUrls.push (url);
}
let fileLink = $('<a>').addClass ('ov_dialog_file_link').appendTo (fileList);
fileLink.attr ('href', url);
@ -272,6 +280,11 @@ OV.ShowExportDialog = function (model, importer)
});
});
dialog.SetCloseHandler (function () {
for (let i = 0; i < createdUrls.length; i++) {
OV.RevokeObjectUrl (createdUrls[i]);
}
});
dialog.Show ();
return dialog;
};

View File

@ -1,24 +1,24 @@
OV.InitModelLoader = function (modelLoader, callbacks)
{
function OpenErrorDialog (importerError)
function OpenErrorDialog (importError)
{
if (importerError.code === OV.ImporterErrorCode.NoImportableFile) {
if (importError.code === OV.ImportErrorCode.NoImportableFile) {
return OV.ShowMessageDialog (
'Something went wrong',
'No importable file found. You can open obj, 3ds, stl, ply, gltf, glb and off files.',
importerError.message
importError.message
);
} else if (importerError.code === OV.ImporterErrorCode.ImportFailed) {
} else if (importError.code === OV.ImportErrorCode.ImportFailed) {
return OV.ShowMessageDialog (
'Something went wrong',
'Failed to import model.',
importerError.message
importError.message
);
} else {
return OV.ShowMessageDialog (
'Something went wrong',
'Unknown error.',
importerError.message
importError.message
);
}
}
@ -53,9 +53,9 @@ OV.InitModelLoader = function (modelLoader, callbacks)
onTextureLoaded : function () {
callbacks.onRender ();
},
onLoadError : function (importerError) {
onLoadError : function (importError) {
progressDialog.Hide ();
errorDialog = OpenErrorDialog (importerError);
errorDialog = OpenErrorDialog (importError);
},
});
};

View File

@ -6,6 +6,7 @@ OV.Modal = class
this.overlayDiv = null;
this.resizeHandler = null;
this.customResizeHandler = null;
this.closeHandler = null;
this.isOpen = false;
this.closeable = true;
}
@ -25,6 +26,11 @@ OV.Modal = class
this.customResizeHandler = customResizeHandler;
}
SetCloseHandler (closeHandler)
{
this.closeHandler = closeHandler;
}
Open ()
{
let windowObj = $(window);
@ -54,6 +60,9 @@ OV.Modal = class
let windowObj = $(window);
windowObj.unbind ('resize', this.resizeHandler);
if (this.closeHandler !== null) {
this.closeHandler ();
}
this.modalDiv.remove ();
this.overlayDiv.remove ();

View File

@ -27,7 +27,7 @@ OV.IsHoverEnabled = function ()
OV.IsSmallWidth = function ()
{
if (window.matchMedia ('(max-width : 600px)').matches) {
if (window.matchMedia ('(max-width : 700px)').matches) {
return true;
} else {
return false;
@ -36,7 +36,7 @@ OV.IsSmallWidth = function ()
OV.IsSmallHeight = function ()
{
if (window.matchMedia ('(max-height : 600px)').matches) {
if (window.matchMedia ('(max-height : 700px)').matches) {
return true;
} else {
return false;
@ -103,8 +103,9 @@ OV.CopyToClipboard = function (text)
OV.CreateIconButton = function (iconName, hoverIconName, title, link)
{
let buttonLink = $('<a>');
buttonLink.attr ('target', '_blank');
buttonLink.attr ('href', link);
buttonLink.attr ('target', '_blank');
buttonLink.attr ('rel', 'noopener noreferrer');
OV.InstallTooltip (buttonLink, title);
let imgElem = $('<img>').attr ('src', iconName).appendTo (buttonLink);
if (hoverIconName !== null && OV.IsHoverEnabled ()) {

View File

@ -627,7 +627,7 @@ div.ov_menu_info_panel div.ov_info_box_button:hover
}
@media only screen and (max-width: 600px)
@media only screen and (max-width: 700px)
{
.only_full_width

View File

@ -71,6 +71,9 @@ OV.Website = class
this.dialog.Hide ();
this.dialog = null;
}
if (this.model !== null) {
OV.RevokeModelUrls (this.model);
}
this.model = null;
this.parameters.introDiv.hide ();
this.ShowViewer (false);
@ -265,7 +268,7 @@ OV.Website = class
});
AddSeparator (this.toolbar, true);
AddButton (this.toolbar, 'export', 'Export model', true, function () {
obj.dialog = OV.ShowExportDialog (obj.model, obj.modelLoader.GetImporter ());
obj.dialog = OV.ShowExportDialog (obj.model);
});
AddButton (this.toolbar, 'embed', 'Get embedding code', true, function () {
obj.dialog = OV.ShowEmbeddingDialog (obj.modelLoader.GetImporter (), obj.viewer.GetCamera ());