Help
RSS
API
Feed
Maltego
Contact
Domain > 3d-model-to-images-converter.com
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2025-06-16
18.64.183.24
(
ClassC
)
2025-06-20
3.161.136.13
(
ClassC
)
2025-09-22
18.160.213.118
(
ClassC
)
2025-11-17
3.169.173.129
(
ClassC
)
Port 80
HTTP/1.1 301 Moved PermanentlyServer: CloudFrontDate: Mon, 17 Nov 2025 06:26:22 GMTContent-Type: text/htmlContent-Length: 167Connection: keep-aliveLocation: https://3d-model-to-images-converter.com/X-Cache: Redirect from cloudfrontVia: 1.1 11017c4db22106ac70e16ce75190a430.cloudfront.net (CloudFront)X-Amz-Cf-Pop: HIO52-P4Alt-Svc: h3:443; ma86400X-Amz-Cf-Id: znoprPkiLEOULVMAlBEB9sdH73pzgZiuwqi6zf2eL7wrOvFOtP0GJg html>head>title>301 Moved Permanently/title>/head>body>center>h1>301 Moved Permanently/h1>/center>hr>center>CloudFront/center>/body>/html>
Port 443
HTTP/1.1 200 OKContent-Type: text/htmlContent-Length: 115091Connection: keep-aliveDate: Mon, 17 Nov 2025 06:26:22 GMTCache-Control: public, max-age0, s-maxage31536000Server: AmazonS3Accept-Ranges: bytesETag: 315efc0dc8cc31be6c93bbaba1757a4dLast-Modified: Wed, 15 Oct 2025 14:03:16 GMTX-Cache: Miss from cloudfrontVia: 1.1 6a31d7747628574e9fa26dd40efa100a.cloudfront.net (CloudFront)X-Amz-Cf-Pop: HIO52-P4Alt-Svc: h3:443; ma86400X-Amz-Cf-Id: WimYHYW_qCKXG74Kub896GbSYP-yD4ybcMfG89UOPgzdynZzoDfu_w !DOCTYPE html>html langen>head> meta charsetUTF-8> meta nameviewport contentwidthdevice-width, initial-scale1.0> meta namekeywords contentonline,3d,model,convert,3ds,gltf,glb,obj,ply,stl,png,gif,animation,pdf,catalog,batch /> meta namedescription contentConvert 3D models (OBJ, STL, GLTF/GLB) to PNG images, 360° GIFs, & PDF catalogs. Free online tool with transparent background & batch processing support.> title>3D Model to Image Converter | PNG, 360° GIF, PDF Catalog Generator/title> link relcanonical hrefhttps://3d-model-to-images-converter.com/ /> link relalternate hreflangen hrefhttps://3d-model-to-images-converter.com/ /> link relalternate hreflangja hrefhttps://3d-model-to-images-converter.com/ja/ /> link relalternate hreflangx-default hrefhttps://3d-model-to-images-converter.com/ /> link relicon hreficon.png typeimage/png> script srchttps://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js>/script> script srchttps://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/GLTFLoader.js>/script> script srchttps://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/STLLoader.js>/script> script srchttps://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/PLYLoader.js>/script> script srchttps://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/loaders/OBJLoader.js>/script> script srchttps://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js>/script> script srchttps://cdn.jsdelivr.net/npm/gifshot@0.4.5/dist/gifshot.min.js>/script> script srchttps://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js>/script> script srchttps://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js>/script> script async srchttps://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?clientca-pub-6371808196726561 crossoriginanonymous>/script> script async srchttps://www.googletagmanager.com/gtag/js?idG-S3EX8KBMC1>/script> script> window.dataLayer window.dataLayer || ; function gtag(){dataLayer.push(arguments);} gtag(js, new Date()); gtag(config, G-S3EX8KBMC1); /script> style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; padding: 20px; display: flex; justify-content: center; align-items: center; } .container { max-width: 1500px; margin: 0 auto; background: rgba(255, 255, 255, 0.98); border-radius: 25px; padding: 30px; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15); border: 1px solid rgba(255, 255, 255, 0.3); } h1 { text-align: center; color: #333; margin-bottom: 10px; font-size: 2.5em; background: linear-gradient(45deg, #667eea, #764ba2); -webkit-background-clip: text; -webkit-text-fill-color: transparent; font-weight: 700; letter-spacing: 1px; } .subtitle { text-align: center; color: #666; margin-bottom: 25px; font-size: 1.1em; font-weight: 500; } .feature-badges { display: flex; justify-content: center; gap: 10px; margin-bottom: 25px; flex-wrap: wrap; } .badge { background: linear-gradient(45deg, #667eea, #764ba2); color: white; padding: 6px 14px; border-radius: 20px; font-size: 0.85em; font-weight: 600; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); } .main-content { display: grid; grid-template-columns: 2fr 1fr; gap: 30px; align-items: start; } .left-panel { display: flex; flex-direction: column; gap: 20px; } .right-panel { background: rgba(255, 255, 255, 0.9); border-radius: 20px; padding: 25px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); position: sticky; top: 20px; border: 1px solid rgba(230, 230, 230, 0.8); } .upload-section { text-align: center; } .upload-box { border: 3px dashed #667eea; border-radius: 20px; padding: 40px; background: rgba(102, 126, 234, 0.08); transition: all 0.4s ease; cursor: pointer; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 180px; } .upload-box:hover { border-color: #764ba2; transform: translateY(-5px); } .upload-icon { font-size: 3em; color: #667eea; margin-bottom: 15px; } .upload-text { font-size: 1.2em; color: #444; font-weight: 600; margin-bottom: 8px; } .upload-hint { font-size: 0.9em; color: #777; font-weight: 500; margin-bottom: 15px; } .batch-info { background: rgba(102, 126, 234, 0.1); padding: 10px; border-radius: 10px; font-size: 0.9em; color: #555; font-weight: 600; } #fileInput { display: none; } .file-list { background: white; border-radius: 15px; padding: 15px; margin-top: 15px; display: none; } .file-item { display: flex; align-items: center; justify-content: space-between; padding: 10px; background: #f8f9fa; border-radius: 8px; margin-bottom: 8px; } .file-item:last-child { margin-bottom: 0; } .file-name { flex: 1; font-size: 0.9em; color: #333; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .file-status { font-size: 0.85em; padding: 4px 10px; border-radius: 12px; font-weight: 600; margin-left: 10px; } .file-status.pending { background: #ffc107; color: #856404; } .file-status.processing { background: #17a2b8; color: white; } .file-status.completed { background: #28a745; color: white; } .file-status.error { background: #dc3545; color: white; } .viewer-container { background: #f0f2f5; border-radius: 20px; padding: 20px; box-shadow: inset 0 3px 15px rgba(0, 0, 0, 0.08); display: none; flex-grow: 1; } #viewer { width: 100%; height: 580px; border-radius: 15px; background: #e9eff3; cursor: grab; } #viewer:active { cursor: grabbing; } .settings-section { margin-bottom: 25px; } .settings-title { font-size: 1.3em; color: #333; margin-bottom: 18px; font-weight: 700; display: flex; align-items: center; gap: 10px; padding-bottom: 10px; border-bottom: 2px solid #eee; } .action-buttons { margin-bottom: 20px; display: flex; flex-direction: column; gap: 10px; } .style-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; margin-bottom: 20px; } .style-option { background: white; border: 2px solid #e9ecef; border-radius: 15px; padding: 15px; text-align: center; cursor: pointer; transition: all 0.3s ease; } .style-option:hover { border-color: #667eea; transform: translateY(-3px); } .style-option.selected { border-color: #667eea; background: rgba(102, 126, 234, 0.15); box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2); } .style-option .icon { font-size: 2em; margin-bottom: 8px; } .style-option .name { font-weight: 700; color: #333; margin-bottom: 5px; font-size: 1em; } .setting-group { margin-bottom: 18px; } .setting-label { display: block; margin-bottom: 8px; color: #555; font-weight: 600; font-size: 0.95em; } .setting-input { width: 100%; padding: 10px 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 1em; } .setting-input:focus { border-color: #667eea; outline: none; box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.2); } .checkbox-group { display: flex; align-items: center; gap: 10px; padding: 12px; background: white; border-radius: 8px; border: 2px solid #e9ecef; cursor: pointer; transition: all 0.3s ease; } .checkbox-group:hover { border-color: #667eea; } .checkbox-group inputtypecheckbox { width: 20px; height: 20px; cursor: pointer; } .color-input { width: 100%; height: 42px; border: 1px solid #ddd; border-radius: 8px; cursor: pointer; -webkit-appearance: none; appearance: none; padding: 0; background: transparent; } .color-input::-webkit-color-swatch-wrapper { padding: 4px; } .color-input::-webkit-color-swatch { border: none; border-radius: 4px; } .color-input::-moz-color-swatch { border: none; border-radius: 4px; } .slider-group { display: flex; align-items: center; gap: 15px; } .slider-input { flex: 1; height: 8px; background: #e0e0e0; border-radius: 5px; outline: none; } .slider-input::-webkit-slider-thumb { -webkit-appearance: none; width: 20px; height: 20px; border-radius: 50%; background: #667eea; cursor: pointer; } .slider-value { min-width: 45px; text-align: right; font-weight: 700; color: #667eea; font-size: 1em; } .angles-section { margin-bottom: 25px; } .angle-options { display: grid; grid-template-columns: repeat(auto-fit, minmax(85px, 1fr)); gap: 10px; } .angle-option { background: white; border: 2px solid #e9ecef; border-radius: 12px; padding: 12px; text-align: center; cursor: pointer; transition: all 0.3s ease; font-size: 0.9em; font-weight: 500; color: #555; } .angle-option:hover { border-color: #667eea; transform: translateY(-2px); } .angle-option.selected { border-color: #667eea; background: rgba(102, 126, 234, 0.15); color: #667eea; font-weight: 700; box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2); } .btn { background: linear-gradient(45deg, #667eea, #764ba2); color: white; border: none; padding: 13px 26px; border-radius: 30px; font-size: 1em; cursor: pointer; transition: all 0.3s ease; margin: 5px 0; box-shadow: 0 6px 15px rgba(102, 126, 234, 0.3); width: 100%; font-weight: 600; display: flex; align-items: center; justify-content: center; gap: 8px; } .btn:hover { transform: translateY(-3px); box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4); } .btn:active { transform: translateY(0); box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2); } .btn:disabled { opacity: 0.6; cursor: not-allowed; transform: none; box-shadow: none; } .btn.generate-btn { font-size: 1.1em; padding: 16px 26px; } .btn.animation-btn { background: linear-gradient(45deg, #ff6b6b, #ee5a6f); } .btn.pdf-btn { background: linear-gradient(45deg, #4ecdc4, #44a08d); } .images-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); gap: 20px; margin-top: 15px; } .image-item { background: white; border-radius: 15px; padding: 15px; box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); transition: transform 0.3s ease; display: flex; flex-direction: column; align-items: center; } .image-item:hover { transform: translateY(-5px); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15); } .image-item img { width: 100%; height: 200px; object-fit: contain; border-radius: 10px; background: #f8f9fa; margin-bottom: 15px; border: 1px solid #eee; } .image-item h3 { text-align: center; margin-bottom: 12px; color: #333; font-size: 1.1em; font-weight: 600; } .download-btn { font-size: 0.9em; padding: 10px 18px; margin-top: auto; } .download-all-btn { background: linear-gradient(45deg, #ffc107, #ff8c00); box-shadow: 0 8px 20px rgba(255, 193, 7, 0.3); } .progress-bar { width: 100%; height: 8px; background: #e9ecef; border-radius: 4px; overflow: hidden; margin: 20px 0; display: none; position: relative; } .progress-fill { height: 100%; background: linear-gradient(45deg, #667eea, #764ba2); transition: width 0.3s ease; width: 0%; } .status { text-align: center; margin: 20px 0; font-weight: 600; color: #555; padding: 15px; border-radius: 12px; font-size: 1em; } .error { color: #dc3545; background: rgba(220, 53, 69, 0.1); border-left: 5px solid #dc3545; } .success { color: #28a745; background: rgba(40, 167, 69, 0.1); border-left: 5px solid #28a745; } .generated-images-notice { background-color: rgba(102, 126, 234, 0.1); border-left: 5px solid #667eea; color: #333; text-align: left; } footer { text-align: center; margin-top: 30px; padding-top: 20px; border-top: 2px solid #eee; color: #666; font-size: 0.9em; } footer a { color: #667eea; text-decoration: none; font-weight: 600; } footer a:hover { text-decoration: underline; } .lang-switcher { position: fixed; top: 20px; right: 20px; z-index: 1000; } .lang-btn { background: white; border: 2px solid #667eea; border-radius: 20px; padding: 10px 20px; cursor: pointer; font-weight: 600; color: #667eea; font-size: 0.9em; transition: all 0.3s ease; box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2); } .lang-btn:hover { background: #667eea; color: white; transform: translateY(-2px); box-shadow: 0 6px 16px rgba(102, 126, 234, 0.3); } @media (max-width: 1200px) { .main-content { grid-template-columns: 1fr; } .right-panel { position: static; margin-top: 30px; } } @media (max-width: 768px) { .container { padding: 15px; } h1 { font-size: 2em; } .upload-box { padding: 20px; } .viewer-container { padding: 10px; } #viewer { height: 40vh; } .settings-title { font-size: 1.1em; } .style-options, .angle-options, .images-grid { grid-template-columns: 1fr; } } /style>/head>body> div classlang-switcher> button classlang-btn idlangBtn>🌐 EN / 日本語/button> /div> div classcontainer> h1 idmainTitle>🎨 3D Model to Images Converter/h1> p classsubtitle idmainSubtitle>Professional Edition - Create Images, Animations & Catalogs/p> div classfeature-badges idfeatureBadges> span classbadge>✨ 360° Animations/span> span classbadge>🎭 Transparent PNG/span> span classbadge>📦 Batch Processing/span> span classbadge>📄 PDF Catalogs/span> /div> div classmain-content> div classleft-panel> div classupload-section> div classupload-box onclickdocument.getElementById(fileInput).click()> div classupload-icon>📁/div> div classupload-text>Upload 3D Model File(s)/div> div classupload-hint>Supported formats: OBJ, STL, PLY, GLTF/GLB/div> div classbatch-info>💡 Select multiple files for batch processing/div> /div> input typefile idfileInput accept.obj,.stl,.ply,.gltf,.glb multiple /> div classfile-list idfileList>/div> /div> div classstatus generated-images-notice idimagesGridNotice styledisplay: none;>/div> div classviewer-container idviewerContainer> div idviewer>/div> /div> div classprogress-bar idprogressBar>div classprogress-fill idprogressFill>/div>/div> div classstatus idstatus>/div> div classimages-grid idimagesGrid>/div> /div> div classright-panel> div classaction-buttons> button classbtn generate-btn idgenerateBtn disabled>📸 Generate Images/button> button classbtn animation-btn idgenerate360Btn disabled>🎬 Generate 360° Animation/button> button classbtn pdf-btn idgeneratePdfBtn styledisplay: none;>📄 Generate PDF Catalog/button> button classbtn download-all-btn iddownloadAllBtn styledisplay: none;>📦 Download All Images/button> /div> div classsettings-section> div classsettings-title>🎨 Style/div> div classstyle-options> div classstyle-option selected data-styleoriginal>div classicon>🖼️/div>div classname>Original/div>/div> div classstyle-option data-stylerealistic>div classicon>🌟/div>div classname>Realistic/div>/div> div classstyle-option data-stylewireframe>div classicon>🔗/div>div classname>Wireframe/div>/div> div classstyle-option data-styletoon>div classicon>🎭/div>div classname>Toon/div>/div> div classstyle-option data-styleclay>div classicon>🏺/div>div classname>Clay/div>/div> /div> /div> div classsettings-section> div classsettings-title>💡 Lighting & Environment/div> div classsetting-group> label classcheckbox-group> input typecheckbox idtransparentBg> span classsetting-label>✨ Transparent Background/span> /label> /div> div classsetting-group> label classcheckbox-group> input typecheckbox idenableShadows> span classsetting-label>🌓 Enable Shadows/Directional Lighting/span> /label> /div> div classsetting-group idbgColorGroup> label classsetting-label idlabelBgColor>Background Color/label> input typecolor classcolor-input idbgColor value#f8f9fa> /div> div classsetting-group> label classsetting-label idlabelLightIntensity>Light Intensity/label> div classslider-group> input typerange classslider-input idlightIntensity min0 max1 step0.01 value0.5> span classslider-value idlightValue>0.50/span> /div> /div> div classsetting-group> label classsetting-label idlabelAmbientLight>Ambient Light (Shadows)/label> div classslider-group> input typerange classslider-input idshadowIntensity min0 max1 step0.01 value0.5> span classslider-value idshadowValue>0.50/span> /div> /div> div classsetting-group> label classsetting-label idlabelLightAzimuth>Light Angle (Horizontal)/label> div classslider-group> input typerange classslider-input idlightAzimuth min-180 max180 step1 value45> span classslider-value idlightAzimuthValue>45°/span> /div> /div> div classsetting-group> label classsetting-label idlabelLightElevation>Light Angle (Vertical)/label> div classslider-group> input typerange classslider-input idlightElevation min0 max90 step1 value45> span classslider-value idlightElevationValue>45°/span> /div> /div> /div> div classangles-section> div classsettings-title>📐 Camera Angles/div> div classangle-options> div classangle-option selected data-anglefront>Current/div> div classangle-option selected data-angleback>Back/div> div classangle-option selected data-angleleft>Left/div> div classangle-option selected data-angleright>Right/div> div classangle-option selected data-angletop>Top/div> div classangle-option data-anglebottom>Bottom/div> div classangle-option data-angleiso1>Iso 1/div> div classangle-option data-angleiso2>Iso 2/div> /div> /div> div classsettings-section> div classsettings-title>🎬 Animation Settings/div> div classsetting-group> label classsetting-label idlabelAnimationType>Animation Type/label> select classsetting-input idanimationType> option valueturntable>🔄 Turntable (Camera Rotation)/option> option valuemodel>🎭 Model Animation/option> /select> /div> div classsetting-group idanimationSelectGroup styledisplay: none;> label classsetting-label idlabelSelectAnimation>Select Animation/label> select classsetting-input idanimationSelect> option valuenone>None (静止状態)/option> /select> /div> div classsetting-group> label classsetting-label idlabelAnimDuration>Animation Duration (seconds)/label> div classslider-group> input typerange classslider-input idanimationDuration min1 max10 step0.5 value3> span classslider-value iddurationValue>3.0s/span> /div> /div> div classsetting-group> label classsetting-label idlabelFramesPerAnim>Frames per Animation/label> select classsetting-input idanimationFrames> option value24>24 frames (Fast)/option> option value36 selected>36 frames (Balanced)/option> option value48>48 frames (Smooth)/option> option value60>60 frames (High Quality)/option> /select> /div> div classsetting-group stylebackground: rgba(102, 126, 234, 0.1); padding: 12px; border-radius: 8px; font-size: 0.9em;> div stylecolor: #555; margin-bottom: 5px;> strong>💡 Calculated:/strong> /div> div stylecolor: #667eea; font-weight: 600; idframeTimeInfo> 83.3ms per frame (~12 fps) /div> /div> div classsetting-group idmodelAnimationInfo styledisplay: none; background: rgba(40, 167, 69, 0.1); padding: 12px; border-radius: 8px; font-size: 0.9em; border-left: 4px solid #28a745;> div stylecolor: #28a745; font-weight: 600;> ✓ Model Animations Available /div> div stylecolor: #555; font-size: 0.85em; margin-top: 5px; idanimationDetails> No animations found /div> /div> /div> div classsettings-section> div classsettings-title>🖼️ Output Settings/div> div classsetting-group> label classsetting-label idlabelResolution>Resolution/label> select classsetting-input idresolution> option value512>512×512/option> option value1024 selected>1024×1024/option> option value2048>2048×2048/option> option value4096>4096×4096/option> /select> /div> /div> /div> /div> footer> p>© 2025 KENTA TAKAHASHI. All Rights Reserved./p> p>Powered by:/p> p> a hrefhttps://threejs.org/ target_blank>Three.js/a> (MIT) | a hrefhttps://stuk.github.io/jszip/ target_blank>JSZip/a> (MIT) | a hrefhttps://github.com/parallax/jsPDF target_blank>jsPDF/a> (MIT) | a hrefhttps://github.com/yahoo/gifshot target_blank>gifshot/a> (MIT) /p> p>a hrefterms-of-use.html>Terms of Use/a> | a hrefprivacy-policy.html>Privacy Policy/a>/p> /footer> /div> script> // Translation Dictionary const translations { en: { pageTitle: 3D Model to Image Converter | PNG, 360° GIF, PDF Catalog Generator, metaDescription: Convert 3D models (OBJ, STL, GLTF/GLB) to PNG images, 360° GIFs, & PDF catalogs. Free online tool with transparent background & batch processing support., mainTitle: 🎨 3D Model to Image Converter, mainSubtitle: PNG, 360° GIF, PDF Catalog Generator - Create Images, Animations & Catalogs, badge1: ✨ 360° Animations, badge2: 🎭 Transparent PNG, badge3: 📦 Batch Processing, badge4: 📄 PDF Catalogs, uploadText: Upload 3D Model File(s), uploadHint: Supported formats: OBJ, STL, PLY, GLTF/GLB, batchInfo: 💡 Select multiple files for batch processing, generateBtn: 📸 Generate Images, generate360Btn: 🎬 Generate 360° Animation, generatePdfBtn: 📄 Generate PDF Catalog, downloadAllBtn: 📦 Download All Images, styleTitle: 🎨 Style, styleOriginal: Original, styleRealistic: Realistic, styleWireframe: Wireframe, styleToon: Toon, styleClay: Clay, lightingTitle: 💡 Lighting & Environment, bgColorLabel: Background Color, transparentBg: ✨ Transparent Background, enableShadows: 🌓 Enable Shadows/Directional Lighting, lightIntensity: Light Intensity, ambientLight: Ambient Light (Shadows), lightAzimuth: Light Angle (Horizontal), lightElevation: Light Angle (Vertical), anglesTitle: 📐 Camera Angles, angleCurrent: Current, angleBack: Back, angleLeft: Left, angleRight: Right, angleTop: Top, angleBottom: Bottom, angleIso1: Iso 1, angleIso2: Iso 2, animationTitle: 🎬 Animation Settings, animationType: Animation Type, animTypeTurntable: 🔄 Turntable (Camera Rotation), animTypeModel: 🎭 Model Animation, selectAnimation: Select Animation, animNone: None (静止状態), animDuration: Animation Duration (seconds), framesPerAnim: Frames per Animation, frames24: 24 frames (Fast), frames36: 36 frames (Balanced), frames48: 48 frames (Smooth), frames60: 60 frames (High Quality), calculatedInfo: 💡 Calculated:, animAvailable: ✓ Model Animations Available, outputTitle: 🖼️ Output Settings, resolution: Resolution, copyright: © 2025 KENTA TAKAHASHI. All Rights Reserved., poweredBy: Powered by:, termsLink: Terms of Use, privacyLink: Privacy Policy, loadingFile: Loading file..., modelLoaded: 3D model loaded successfully!, generateSuccess: images generated successfully!, errorNoModel: Please upload a 3D model first., errorNoAngles: Please select angles to generate images., errorFileSize: File size exceeds 50MB., errorNoAnimation: GIF library not loaded. Please refresh the page and try again., processingBatch: Processing batch files..., batchComplete: Batch processing complete!, creatingZip: Creating ZIP file..., zipDownloaded: All images downloaded as ZIP!, generatingPdf: Generating PDF catalog..., pdfGenerated: PDF catalog generated and downloaded! }, ja: { pageTitle: 3DファイルをPNG/GIF/PDF化 | OBJ/STL/GLTF/GLB対応の無料変換ツール, metaDescription: OBJ/STL/GLTF/GLBなどの3Dモデルファイルを高品質なPNG画像、360°回転GIF、PDFカタログに変換。透過背景対応、バッチ処理可能な無料オンラインツール。, mainTitle: 🎨 3DファイルをPNG/GIF/PDF化, mainSubtitle: OBJ/STL/GLTF/GLB対応の無料変換ツール - 画像、アニメーション、カタログを作成, badge1: ✨ 360°アニメーション, badge2: 🎭 透過PNG, badge3: 📦 バッチ処理, badge4: 📄 PDFカタログ, uploadText: 3Dモデルファイルをアップロード, uploadHint: 対応形式:OBJ、STL、PLY、GLTF/GLB, batchInfo: 💡 複数ファイルを選択してバッチ処理, generateBtn: 📸 画像を生成, generate360Btn: 🎬 360°アニメーションを生成, generatePdfBtn: 📄 PDFカタログを生成, downloadAllBtn: 📦 すべてダウンロード, styleTitle: 🎨 スタイル, styleOriginal: オリジナル, styleRealistic: リアル, styleWireframe: ワイヤーフレーム, styleToon: トゥーン, styleClay: クレイ, lightingTitle: 💡 照明と環境, bgColorLabel: 背景色, transparentBg: ✨ 透過背景, enableShadows: 🌓 陰影・指向性照明を有効化, lightIntensity: ライトの強度, ambientLight: 環境光(影), lightAzimuth: ライトの角度(水平), lightElevation: ライトの角度(垂直), anglesTitle: 📐 カメラアングル, angleCurrent: 正面, angleBack: 背面, angleLeft: 左, angleRight: 右, angleTop: 上, angleBottom: 下, angleIso1: 斜め1, angleIso2: 斜め2, animationTitle: 🎬 アニメーション設定, animationType: アニメーションタイプ, animTypeTurntable: 🔄 ターンテーブル(カメラ回転), animTypeModel: 🎭 モデルアニメーション, selectAnimation: アニメーションを選択, animNone: なし(静止状態), animDuration: アニメーション時間(秒), framesPerAnim: フレーム数, frames24: 24フレーム(高速), frames36: 36フレーム(バランス), frames48: 48フレーム(スムーズ), frames60: 60フレーム(高品質), calculatedInfo: 💡 計算結果:, animAvailable: ✓ モデルアニメーション利用可能, outputTitle: 🖼️ 出力設定, resolution: 解像度, copyright: © 2025 高橋 健太. All Rights Reserved., poweredBy: 使用ライブラリ:, termsLink: 利用規約, privacyLink: プライバシーポリシー, loadingFile: ファイルを読み込んでいます..., modelLoaded: 3Dモデルを正常に読み込みました!, generateSuccess: 枚の画像を生成しました!, errorNoModel: 先に3Dモデルをアップロードしてください。, errorNoAngles: 画像を生成するアングルを選択してください。, errorFileSize: ファイルサイズが50MBを超えています。, errorNoAnimation: GIFライブラリが読み込まれていません。ページを再読み込みしてください。, processingBatch: バッチファイルを処理しています..., batchComplete: バッチ処理が完了しました!, creatingZip: ZIPファイルを作成しています..., zipDownloaded: すべての画像をZIPでダウンロードしました!, generatingPdf: PDFカタログを生成しています..., pdfGenerated: PDFカタログを生成してダウンロードしました! } }; // Language Manager class LanguageManager { constructor() { this.currentLang this.detectLanguage(); this.init(); } detectLanguage() { // URLパスをチェック if (window.location.pathname.startsWith(/ja/)) { return ja; } // Check sessionStorage first const stored sessionStorage.getItem(language); if (stored && en, ja.includes(stored)) { return stored; } // Detect from browser settings const browserLang (navigator.language || navigator.userLanguage).split(-)0; return browserLang ja ? ja : en; } init() { try { this.setLanguage(this.currentLang); this.setupToggleButton(); } catch (error) { console.error(Language initialization error:, error); // Fallback to English this.currentLang en; try { this.setLanguage(en); this.setupToggleButton(); } catch (fallbackError) { console.error(Fallback language initialization error:, fallbackError); } } } setupToggleButton() { const btn document.getElementById(langBtn); if (!btn) { console.warn(Language toggle button not found); return; } btn.addEventListener(click, () > { this.currentLang this.currentLang ja ? en : ja; this.setLanguage(this.currentLang); sessionStorage.setItem(language, this.currentLang); // Update button text btn.textContent this.currentLang ja ? 🌐 EN / 日本語 : 🌐 日本語 / EN; }); // Set initial button text btn.textContent this.currentLang ja ? 🌐 EN / 日本語 : 🌐 日本語 / EN; } setLanguage(lang) { const t translationslang; if (!t) return; // Update HTML lang attribute document.documentElement.lang lang; // Update meta tags - with null checks const pageTitle document.getElementById(pageTitle); const metaDesc document.getElementById(metaDescription); if (pageTitle) pageTitle.textContent t.pageTitle; if (metaDesc) metaDesc.setAttribute(content, t.metaDescription); document.title t.pageTitle; // Update main content - with null checks const mainTitle document.getElementById(mainTitle); const mainSubtitle document.getElementById(mainSubtitle); if (mainTitle) mainTitle.textContent t.mainTitle; if (mainSubtitle) mainSubtitle.textContent t.mainSubtitle; // Update badges - with null checks const featureBadges document.getElementById(featureBadges); if (featureBadges) { const badges featureBadges.children; if (badges0) badges0.textContent t.badge1; if (badges1) badges1.textContent t.badge2; if (badges2) badges2.textContent t.badge3; if (badges3) badges3.textContent t.badge4; } // Update upload section - with null checks const uploadText document.querySelector(.upload-text); const uploadHint document.querySelector(.upload-hint); const batchInfo document.querySelector(.batch-info); if (uploadText) uploadText.textContent t.uploadText; if (uploadHint) uploadHint.textContent t.uploadHint; if (batchInfo) batchInfo.textContent t.batchInfo; // Update buttons - with null checks const updateBtn (id, text) > { const btn document.getElementById(id); if (btn) btn.innerHTML text; }; updateBtn(generateBtn, t.generateBtn); updateBtn(generate360Btn, t.generate360Btn); updateBtn(generatePdfBtn, t.generatePdfBtn); updateBtn(downloadAllBtn, t.downloadAllBtn); // Update settings titles this.updateSettingsTitles(t); // Update style options this.updateStyleOptions(t); // Update angle options this.updateAngleOptions(t); // Update form labels this.updateFormLabels(t); // Update footer this.updateFooter(t); // Store translation for converter class window.currentTranslations t; } updateSettingsTitles(t) { const titles document.querySelectorAll(.settings-title); if (titles.length > 5) { if (titles0) titles0.innerHTML t.styleTitle; if (titles1) titles1.innerHTML t.lightingTitle; if (titles2) titles2.innerHTML t.anglesTitle; if (titles3) titles3.innerHTML t.animationTitle; if (titles4) titles4.innerHTML t.outputTitle; } } updateStyleOptions(t) { const styles original, realistic, wireframe, toon, clay; const keys styleOriginal, styleRealistic, styleWireframe, styleToon, styleClay; styles.forEach((style, i) > { const el document.querySelector(`data-style${style} .name`); if (el && tkeysi) el.textContent tkeysi; }); } updateAngleOptions(t) { const angles front, back, left, right, top, bottom, iso1, iso2; const keys angleCurrent, angleBack, angleLeft, angleRight, angleTop, angleBottom, angleIso1, angleIso2; angles.forEach((angle, i) > { const el document.querySelector(`data-angle${angle}`); if (el && tkeysi) el.textContent tkeysi; }); } updateFormLabels(t) { // Update labels by ID - with null checks const labelIds { labelBgColor: bgColorLabel, labelLightIntensity: lightIntensity, labelAmbientLight: ambientLight, labelLightAzimuth: lightAzimuth, labelLightElevation: lightElevation, labelAnimationType: animationType, labelSelectAnimation: selectAnimation, labelAnimDuration: animDuration, labelFramesPerAnim: framesPerAnim, labelResolution: resolution }; for (const id, key of Object.entries(labelIds)) { const label document.getElementById(id); if (label && tkey) { label.textContent tkey; } } // Transparent background checkbox label const transparentLabel document.querySelector(label.checkbox-group .setting-label); if (transparentLabel && t.transparentBg) { transparentLabel.textContent t.transparentBg; } const shadowsLabel document.querySelectorAll(label.checkbox-group .setting-label)1; if (shadowsLabel && t.enableShadows) { shadowsLabel.textContent t.enableShadows; } // Animation type select const animTypeSelect document.getElementById(animationType); if (animTypeSelect && animTypeSelect.options.length > 2) { if (t.animTypeTurntable) animTypeSelect.options0.textContent t.animTypeTurntable; if (t.animTypeModel) animTypeSelect.options1.textContent t.animTypeModel; } // Animation select (None option) const animSelect document.getElementById(animationSelect); if (animSelect && animSelect.options.length > 0 && t.animNone) { animSelect.options0.textContent t.animNone; } // Frames select const framesSelect document.getElementById(animationFrames); if (framesSelect && framesSelect.options.length > 4) { if (t.frames24) framesSelect.options0.textContent t.frames24; if (t.frames36) framesSelect.options1.textContent t.frames36; if (t.frames48) framesSelect.options2.textContent t.frames48; if (t.frames60) framesSelect.options3.textContent t.frames60; } // Update calculated info text const calcInfoElements document.querySelectorAll(.setting-group style*color: #555); calcInfoElements.forEach(el > { if (el && t.calculatedInfo) { const text el.textContent || ; if (text.includes(Calculated) || text.includes(計算結果)) { el.innerHTML `strong>${t.calculatedInfo}/strong>`; } } }); // Update model animation info title const animInfoTitle document.querySelector(#modelAnimationInfo > div:first-child); if (animInfoTitle && t.animAvailable) { const currentText animInfoTitle.textContent || ; if (currentText.includes(Model Animation) || currentText.includes(モデルアニメーション)) { animInfoTitle.textContent t.animAvailable; } } } updateFooter(t) { const footer document.querySelector(footer); if (!footer) return; const paragraphs footer.querySelectorAll(p); if (paragraphs.length > 4) { if (paragraphs0) paragraphs0.textContent t.copyright; if (paragraphs1) paragraphs1.textContent t.poweredBy; // Update links in the last paragraph if (paragraphs3) { const links paragraphs3.querySelectorAll(a); if (links0) links0.textContent t.termsLink; if (links1) links1.textContent t.privacyLink; } } } } // Initialize language manager let langManager; // Wait for DOM to be fully loaded if (document.readyState loading) { document.addEventListener(DOMContentLoaded, () > { langManager new LanguageManager(); }); } else { // DOM is already loaded langManager new LanguageManager(); } class Model3DConverter { constructor() { this.scene null; this.camera null; this.renderer null; this.controls null; this.model null; this.mixer null; // For GLTF animations this.animations ; // Store GLTF animations this.clock new THREE.Clock(); this.generatedImages ; this.generatedGifs ; this.isBatchProcessing false; this.selectedStyle original; this.selectedAngles front, back, left, right, top; this.uploadedFiles ; this.currentFileIndex 0; this.currentModelName model; this.renderSettings { bgColor: #f8f9fa, lightIntensity: 0.5, shadowIntensity: 0.5, resolution: 1024, lightAzimuth: 45, lightElevation: 45, transparentBg: false, enableShadows: false, animationType: turntable, animationDuration: 3.0, animationFrames: 36, selectedAnimationIndex: -1 // -1 none/静止 }; this.init(); } init() { this.setupEventListeners(); this.updateViewerBackground(); this.updateFrameTimeInfo(); } setupEventListeners() { document.getElementById(fileInput).addEventListener(change, (e) > this.handleFileUpload(e)); document.querySelectorAll(.style-option).forEach(option > { option.addEventListener(click, () > this.selectStyle(option.dataset.style)); }); document.querySelectorAll(.angle-option).forEach(option > { option.addEventListener(click, () > this.toggleAngle(option.dataset.angle)); }); document.getElementById(transparentBg).addEventListener(change, (e) > { this.renderSettings.transparentBg e.target.checked; document.getElementById(bgColorGroup).style.display e.target.checked ? none : block; this.updateSceneBackground(); this.updateViewerBackground(); }); document.getElementById(enableShadows).addEventListener(change, (e) > { this.renderSettings.enableShadows e.target.checked; this.updateLighting(); }); document.getElementById(bgColor).addEventListener(input, (e) > { this.renderSettings.bgColor e.target.value; this.updateSceneBackground(); this.updateViewerBackground(); }); document.getElementById(lightIntensity).addEventListener(input, (e) > { const value parseFloat(e.target.value); this.renderSettings.lightIntensity value; document.getElementById(lightValue).textContent value.toFixed(2); this.updateLighting(); }); document.getElementById(shadowIntensity).addEventListener(input, (e) > { const value parseFloat(e.target.value); this.renderSettings.shadowIntensity value; document.getElementById(shadowValue).textContent value.toFixed(2); this.updateLighting(); }); document.getElementById(lightAzimuth).addEventListener(input, (e) > { const value parseInt(e.target.value); this.renderSettings.lightAzimuth value; document.getElementById(lightAzimuthValue).textContent `${value}°`; this.updateLighting(); }); document.getElementById(lightElevation).addEventListener(input, (e) > { const value parseInt(e.target.value); this.renderSettings.lightElevation value; document.getElementById(lightElevationValue).textContent `${value}°`; this.updateLighting(); }); document.getElementById(resolution).addEventListener(change, (e) > { this.renderSettings.resolution parseInt(e.target.value); }); document.getElementById(animationType).addEventListener(change, (e) > { this.renderSettings.animationType e.target.value; this.updateAnimationTypeUI(); }); document.getElementById(animationSelect).addEventListener(change, (e) > { const value e.target.value; this.renderSettings.selectedAnimationIndex value none ? -1 : parseInt(value); this.updateViewerAnimation(); }); document.getElementById(animationDuration).addEventListener(input, (e) > { const value parseFloat(e.target.value); this.renderSettings.animationDuration value; document.getElementById(durationValue).textContent `${value.toFixed(1)}s`; this.updateFrameTimeInfo(); }); document.getElementById(animationFrames).addEventListener(change, (e) > { this.renderSettings.animationFrames parseInt(e.target.value); this.updateFrameTimeInfo(); }); document.getElementById(generateBtn).addEventListener(click, () > this.generateImages()); document.getElementById(generate360Btn).addEventListener(click, () > this.generate360Animation()); document.getElementById(generatePdfBtn).addEventListener(click, () > this.generatePdfCatalog()); document.getElementById(downloadAllBtn).addEventListener(click, () > this.downloadAllImages()); } updateAnimationTypeUI() { const isModelAnimation this.renderSettings.animationType model; const infoDiv document.getElementById(modelAnimationInfo); const selectGroup document.getElementById(animationSelectGroup); if (isModelAnimation) { selectGroup.style.display block; if (this.animations.length > 0) { infoDiv.style.display block; infoDiv.style.background rgba(40, 167, 69, 0.1); infoDiv.style.borderLeft 4px solid #28a745; } else { infoDiv.style.display block; infoDiv.style.background rgba(255, 193, 7, 0.1); infoDiv.style.borderLeft 4px solid #ffc107; document.getElementById(animationDetails).innerHTML span stylecolor: #856404;>⚠️ No animations in model. Use None (静止状態) for static turntable./span>; } } else { infoDiv.style.display none; selectGroup.style.display none; } } updateViewerAnimation() { if (!this.mixer || this.animations.length 0) return; // Stop all actions this.mixer.stopAllAction(); const index this.renderSettings.selectedAnimationIndex; if (index > 0 && index this.animations.length) { // Play selected animation const action this.mixer.clipAction(this.animationsindex); action.play(); console.log(`Playing animation ${index}: ${this.animationsindex.name}`); } // If index is -1 (none), no animation plays (静止状態) } updateFrameTimeInfo() { const duration this.renderSettings.animationDuration; const frames this.renderSettings.animationFrames; const timePerFrame (duration / frames) * 1000; // milliseconds const fps frames / duration; document.getElementById(frameTimeInfo).textContent `${timePerFrame.toFixed(1)}ms per frame (~${fps.toFixed(1)} fps)`; } selectStyle(style) { document.querySelectorAll(.style-option).forEach(option > option.classList.remove(selected)); document.querySelector(`data-style${style}`).classList.add(selected); this.selectedStyle style; this.updateMaterialStyle(); } toggleAngle(angle) { const element document.querySelector(`.angle-optiondata-angle${angle}`); if(element) element.classList.toggle(selected); const angleIndex this.selectedAngles.indexOf(angle); if (angleIndex > -1) { this.selectedAngles.splice(angleIndex, 1); } else { this.selectedAngles.push(angle); } } async handleFileUpload(event) { const files Array.from(event.target.files); if (files.length 0) return; this.uploadedFiles files; this.currentFileIndex 0; // 複数ファイル読み込み時は既存のモデルをクリア if (files.length > 1) { if (this.model) { this.scene.remove(this.model); this.model null; } this.generatedImages ; document.getElementById(imagesGrid).innerHTML ; document.getElementById(viewerContainer).style.display none; } if (files.length 1) { this.generatedImages ; document.getElementById(imagesGrid).innerHTML ; } document.getElementById(imagesGridNotice).style.display none; document.getElementById(downloadAllBtn).style.display none; document.getElementById(generatePdfBtn).style.display none; this.displayFileList(); if (files.length 1) { await this.processSingleFile(files0, 0); } else { this.showStatus(`${files.length} files ready for batch processing. Click Generate Images to process all.`); document.getElementById(generateBtn).disabled false; document.getElementById(generate360Btn).disabled false; } } displayFileList() { const fileList document.getElementById(fileList); fileList.innerHTML ; fileList.style.display block; this.uploadedFiles.forEach((file, index) > { const fileItem document.createElement(div); fileItem.className file-item; fileItem.innerHTML ` span classfile-name>${file.name}/span> span classfile-status pending idfileStatus${index}>Pending/span> `; fileList.appendChild(fileItem); }); } updateFileStatus(index, status, text) { const statusEl document.getElementById(`fileStatus${index}`); if (statusEl) { statusEl.className `file-status ${status}`; statusEl.textContent text; } } async processSingleFile(file, index) { if (file.size > 50 * 1024 * 1024) { this.showError(this.t(errorFileSize)); this.updateFileStatus(index, error, Too large); return false; } // Store current model name - preserve Japanese characters properly const fileNameWithoutExt file.name.replace(/\.^/.+$/, ); // Convert to safe filename: keep alphanumeric and Japanese, replace others with underscore this.currentModelName fileNameWithoutExt.replace(/^\w\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FAF-/g, _); // If result is empty or only underscores, use a default name if (!this.currentModelName || /^_+$/.test(this.currentModelName)) { this.currentModelName model_ + Date.now(); } this.updateFileStatus(index, processing, Loading...); this.showStatus(this.t(loadingFile)); document.getElementById(viewerContainer).style.display block; try { const arrayBuffer await file.arrayBuffer(); await this.loadModel(arrayBuffer, file.name); this.showSuccess(this.t(modelLoaded)); this.updateFileStatus(index, completed, Loaded); document.getElementById(generateBtn).disabled false; document.getElementById(generate360Btn).disabled false; return true; } catch (error) { console.error(error); this.showError(`Failed to load ${file.name}: ` + error.message); this.updateFileStatus(index, error, Failed); return false; } } async loadModel(arrayBuffer, filename) { this.setupScene(); const extension filename.split(.).pop().toLowerCase(); if (this.model) this.scene.remove(this.model); // Reset animation data this.animations ; this.mixer null; this.renderSettings.selectedAnimationIndex -1; document.getElementById(modelAnimationInfo).style.display none; document.getElementById(animationSelectGroup).style.display none; // Reset animation selector const selectElement document.getElementById(animationSelect); selectElement.innerHTML option valuenone>None (静止状態)/option>; const loaderMap { obj: this.loadOBJ, stl: this.loadSTL, ply: this.loadPLY, gltf: this.loadGLTF, glb: this.loadGLTF }; if (!loaderMapextension) throw new Error(Unsupported file format.); await loaderMapextension.call(this, arrayBuffer); this.centerAndScaleModel(); this.setupOrbitControls(); this.updateMaterialStyle(); this.updateAnimationTypeUI(); this.animate(); } setupScene() { const container document.getElementById(viewer); container.innerHTML ; const { clientWidth: width, clientHeight: height } container; this.scene new THREE.Scene(); this.updateSceneBackground(); this.camera new THREE.PerspectiveCamera(75, width / height, 0.1, 1000); this.renderer new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true, alpha: true }); this.renderer.setSize(width, height); this.renderer.shadowMap.enabled true; this.renderer.shadowMap.type THREE.PCFSoftShadowMap; container.appendChild(this.renderer.domElement); this.updateLighting(); this.clock new THREE.Clock(); // Reset clock } setupOrbitControls() { if (this.controls) this.controls.dispose(); this.controls new THREE.OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping true; } updateLighting() { if (!this.scene) return; const lights this.scene.children.filter(child > child.isLight); lights.forEach(light > this.scene.remove(light)); if (this.renderSettings.enableShadows) { // 陰影あり:指向性ライト + 環境光 this.scene.add(new THREE.AmbientLight(0xffffff, this.renderSettings.shadowIntensity)); const finalLightIntensity this.renderSettings.lightIntensity * 10.0; const directionalLight new THREE.DirectionalLight(0xffffff, finalLightIntensity); const { lightAzimuth, lightElevation } this.renderSettings; const phi THREE.MathUtils.degToRad(90 - lightElevation); const theta THREE.MathUtils.degToRad(lightAzimuth); directionalLight.position.setFromSphericalCoords(20, phi, theta); directionalLight.castShadow true; directionalLight.shadow.mapSize.set(2048, 2048); this.scene.add(directionalLight); } else { // 陰影なし:全方向から均等な照明(スライダー値を反映) const ambientIntensity this.renderSettings.shadowIntensity * 1.5; this.scene.add(new THREE.AmbientLight(0xffffff, ambientIntensity)); // 6方向からの均等なライト(lightIntensityを反映) const directionalIntensity this.renderSettings.lightIntensity * 8.0; const positions 10, 0, 0, // 右 -10, 0, 0, // 左 0, 10, 0, // 上 0, -10, 0, // 下 0, 0, 10, // 前 0, 0, -10 // 後 ; positions.forEach(pos > { const light new THREE.DirectionalLight(0xffffff, directionalIntensity); light.position.set(pos0, pos1, pos2); this.scene.add(light); }); } } updateSceneBackground() { if (this.scene) { if (this.renderSettings.transparentBg) { this.scene.background null; } else { this.scene.background new THREE.Color(this.renderSettings.bgColor); } } } updateMaterialStyle() { if (!this.model) return; this.model.traverse((child) > { if (child.isMesh && child.userData.originalMaterial) { let materialToUse; if (this.selectedStyle original) { materialToUse child.userData.originalMaterial.clone(); } else { const color child.userData.originalMaterial.color || new THREE.Color(0xcccccc); switch (this.selectedStyle) { case realistic: materialToUse new THREE.MeshPhongMaterial({ color }); break; case wireframe: materialToUse new THREE.MeshBasicMaterial({ color: 0x333333, wireframe: true }); break; case toon: materialToUse new THREE.MeshToonMaterial({ color }); break; case clay: materialToUse new THREE.MeshLambertMaterial({ color: 0xd4af37 }); break; default: materialToUse child.userData.originalMaterial.clone(); } } child.material materialToUse; } }); } storeOriginalMaterial(model){ model.traverse((child) > { if (child.isMesh) { child.castShadow true; child.receiveShadow true; child.userData.originalMaterial child.material ? child.material.clone() : new THREE.MeshStandardMaterial({color: 0xcccccc}); } }); } async loadOBJ(b) { this.model new THREE.OBJLoader().parse(new TextDecoder().decode(b)); this.storeOriginalMaterial(this.model); this.scene.add(this.model); } async loadSTL(b) { this.model new THREE.Mesh(new THREE.STLLoader().parse(b), new THREE.MeshPhongMaterial({color:0xcccccc})); this.storeOriginalMaterial(this.model); this.scene.add(this.model); } async loadPLY(b) { const g new THREE.PLYLoader().parse(b); this.model new THREE.Mesh(g, new THREE.MeshPhongMaterial({vertexColors:!!g.attributes.color,color:0xcccccc})); this.storeOriginalMaterial(this.model); this.scene.add(this.model); } async loadGLTF(b) { return new Promise((res, rej) > { new THREE.GLTFLoader().parse(b, , (gltf) > { this.model gltf.scene; this.storeOriginalMaterial(this.model); this.scene.add(this.model); // Check for animations this.animations gltf.animations || ; // Build animation selector const selectElement document.getElementById(animationSelect); selectElement.innerHTML option valuenone>None (静止状態)/option>; if (this.animations.length > 0) { console.log(`Found ${this.animations.length} animation(s) in GLTF:`, this.animations.map(a > a.name)); // Setup animation mixer this.mixer new THREE.AnimationMixer(this.model); // Add options for each animation this.animations.forEach((anim, index) > { const option document.createElement(option); option.value index; option.textContent `${index + 1}. ${anim.name || Animation + (index + 1)} (${anim.duration.toFixed(2)}s)`; selectElement.appendChild(option); }); // Set default to first animation selectElement.value 0; this.renderSettings.selectedAnimationIndex 0; // Play first animation const action this.mixer.clipAction(this.animations0); action.play(); // Update UI const details document.getElementById(animationDetails); const animNames this.animations.map((a, i) > `${i + 1}. ${a.name || Animation + (i + 1)} (${a.duration.toFixed(2)}s)`).join(br>); details.innerHTML `Found ${this.animations.length} animation(s):br>span stylefont-size: 0.8em;>${animNames}/span>`; document.getElementById(modelAnimationInfo).style.display block; document.getElementById(modelAnimationInfo).style.background rgba(40, 167, 69, 0.1); document.getElementById(modelAnimationInfo).style.borderLeft 4px solid #28a745; } else { console.log(No animations found in GLTF); this.mixer null; this.renderSettings.selectedAnimationIndex -1; } res(); }, rej); }); } centerAndScaleModel() { if (!this.model) return; const box new THREE.Box3().setFromObject(this.model); const center box.getCenter(new THREE.Vector3()); const size box.getSize(new THREE.Vector3()); this.model.position.sub(center); const maxDim Math.max(size.x, size.y, size.z); const scale 5 / maxDim; this.model.scale.setScalar(scale); const distance Math.abs((size.length() * scale) / 2 / Math.tan(this.camera.fov * Math.PI / 360)); this.camera.position.set(0, 0, distance * 1.5); this.camera.lookAt(0, 0, 0); } animate() { if (!this.renderer) return; requestAnimationFrame(() > this.animate()); const delta this.clock.getDelta(); // Update animation mixer if exists if (this.mixer) { this.mixer.update(delta); } if (this.controls) this.controls.update(); this.renderer.render(this.scene, this.camera); } updateViewerBackground() { const container document.getElementById(viewerContainer); if (container) { if (this.renderSettings.transparentBg) { container.style.background #e9eff3; } else { const bgColor new THREE.Color(this.renderSettings.bgColor); container.style.background bgColor.clone().lerp(new THREE.Color(0xffffff), 0.1).getStyle(); } } } async generateImages() { // バッチ処理中は通常のチェックをスキップ if (!this.isBatchProcessing) { // 複数ファイルがある場合は常にバッチ処理(モデルの有無に関わらず) if (this.uploadedFiles.length > 1) { await this.processBatchFiles(); return; } // 単一ファイルでモデルがない場合 if (!this.model) { this.showError(this.t(errorNoModel)); return; } } if (!this.model) return; // バッチ処理中でモデルがない場合 if (this.selectedAngles.length 0) { this.showError(this.t(errorNoAngles)); return; } // バッチ処理時は現在の設定を確実に適用 this.updateMaterialStyle(); this.updateLighting(); this.updateSceneBackground(); this.isGeneratingImages true; if (this.controls) this.controls.enabled false; document.getElementById(generateBtn).disabled true; this.showStatus(Generating images...); this.showProgress(0); const { resolution } this.renderSettings; const tempRenderer new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true, alpha: this.renderSettings.transparentBg }); tempRenderer.setSize(resolution, resolution); tempRenderer.shadowMap.enabled true; const baseCamera this.camera.clone(); // Get current model name for batch processing const currentModelName this.currentModelName || model; for (let i 0; i this.selectedAngles.length; i++) { const angle this.selectedAnglesi; const tempCamera this.getCameraForAngle(angle, baseCamera); tempCamera.aspect 1.0; tempCamera.updateProjectionMatrix(); if (this.renderSettings.transparentBg) { tempRenderer.setClearColor(0x000000, 0); } else { tempRenderer.setClearColor(this.renderSettings.bgColor); } tempRenderer.render(this.scene, tempCamera); const dataURL tempRenderer.domElement.toDataURL(image/png); this.generatedImages.push({ angle, dataURL, name: `${currentModelName}_${this.getAngleName(angle)}.png` }); this.showProgress((i + 1) / this.selectedAngles.length * 100); await new Promise(res > setTimeout(res, 10)); } tempRenderer.dispose(); this.displayGeneratedImages(); this.showSuccess(`${this.selectedAngles.length} images generated successfully!`); this.hideProgress(); this.isGeneratingImages false; if (this.controls) this.controls.enabled true; document.getElementById(generateBtn).disabled false; } async processBatchFiles() { this.showStatus(Starting batch processing...); document.getElementById(generateBtn).disabled true; document.getElementById(generate360Btn).disabled true; this.isBatchProcessing true; // ← 追加 let totalImagesGenerated 0; for (let i 0; i this.uploadedFiles.length; i++) { const file this.uploadedFilesi; this.showStatus(`Processing file ${i + 1}/${this.uploadedFiles.length}: ${file.name}`); const success await this.processSingleFile(file, i); if (success) { this.updateFileStatus(i, processing, Generating...); const imagesBefore this.generatedImages.length; await this.generateImages(); const imagesAfter this.generatedImages.length; totalImagesGenerated + (imagesAfter - imagesBefore); this.updateFileStatus(i, completed, `${imagesAfter - imagesBefore} images`); } await new Promise(res > setTimeout(res, 500)); } this.isBatchProcessing false; // ← 追加 this.showSuccess(`Batch processing complete! ${totalImagesGenerated} images generated from ${this.uploadedFiles.length} models.`); document.getElementById(generateBtn).disabled false; document.getElementById(generate360Btn).disabled false; } async generate360Animation() { // バッチ処理中は通常のチェックをスキップ if (!this.isBatchProcessing) { // 複数ファイルがある場合は常にバッチ処理(モデルの有無に関わらず) if (this.uploadedFiles.length > 1) { await this.processBatchAnimations(); return; } // 単一ファイルでモデルがない場合 if (!this.model) { this.showError(Please upload a 3D model first.); return; } } if (!this.model) return; // バッチ処理中でモデルがない場合 // 単一モデルのアニメーション生成 await this.generateSingleAnimation(); } async generateSingleAnimation(isBatch false) { if (!this.model) { this.showError(Please upload a 3D model first.); return null; } if (typeof gifshot undefined) { this.showError(GIF library not loaded. Please refresh the page and try again.); console.error(gifshot library is not loaded); return null; } // バッチ処理時は現在の設定を確実に適用 if (isBatch) { this.updateMaterialStyle(); this.updateLighting(); this.updateSceneBackground(); } const selectedAnimIndex this.renderSettings.selectedAnimationIndex; if (this.renderSettings.animationType model && selectedAnimIndex -1) { this.renderSettings.animationType turntable; console.log(Model animation mode with None selected - switching to turntable); } if (!isBatch && this.controls) this.controls.enabled false; if (!isBatch) document.getElementById(generate360Btn).disabled true; if (!isBatch) { this.showStatus(Preparing animation...); this.showProgress(0); } const frames this.renderSettings.animationFrames; const duration this.renderSettings.animationDuration; const { resolution } this.renderSettings; const isTurntable this.renderSettings.animationType turntable; const frameInterval duration / frames; console.log(`Animation settings: ${isTurntable ? Turntable : Model Animation}, ${frames} frames, ${duration}s total, ${(frameInterval * 1000).toFixed(1)}ms per frame`); const tempRenderer new THREE.WebGLRenderer({ antialias: true, preserveDrawingBuffer: true, alpha: this.renderSettings.transparentBg }); tempRenderer.setSize(resolution, resolution); tempRenderer.shadowMap.enabled true; tempRenderer.shadowMap.type THREE.PCFSoftShadowMap; const baseCamera this.camera.clone(); baseCamera.aspect 1.0; baseCamera.updateProjectionMatrix(); const target this.controls ? this.controls.target.clone() : new THREE.Vector3(0, 0, 0); const originalPosition this.model.position.clone(); const originalRotation this.model.rotation.clone(); const originalScale this.model.scale.clone(); const frameImages ; let tempMixer null; let action null; let animationClip null; if (!isTurntable && this.animations.length > 0 && selectedAnimIndex > 0) { animationClip this.animationsselectedAnimIndex; tempMixer new THREE.AnimationMixer(this.model); action tempMixer.clipAction(animationClip); action.play(); action.paused true; tempMixer.setTime(0); console.log(`Using animation: ${animationClip.name} (${animationClip.duration.toFixed(2)}s)`); } if (!isBatch) this.showStatus(`Capturing animation frames... 0/${frames}`); console.log(`Starting frame capture: ${frames} frames for ${isTurntable ? 360° rotation : model animation}`); for (let i 0; i frames; i++) { let tempCamera; if (isTurntable) { const angle (i / frames) * Math.PI * 2; const degrees (i / frames) * 360; if (i 0 || i frames - 1 || i Math.floor(frames / 2)) { console.log(`Frame ${i}: ${degrees.toFixed(1)}° (${angle.toFixed(3)} rad)`); } tempCamera baseCamera.clone(); tempCamera.aspect 1.0; tempCamera.updateProjectionMatrix(); const cameraOffset baseCamera.position.clone().sub(target); const rotation new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), angle); cameraOffset.applyQuaternion(rotation); tempCamera.position.copy(target).add(cameraOffset); tempCamera.lookAt(target); } else { tempCamera baseCamera.clone(); tempCamera.aspect 1.0; tempCamera.updateProjectionMatrix(); this.model.position.copy(originalPosition); this.model.rotation.copy(originalRotation); this.model.scale.copy(originalScale); if (animationClip) { const normalizedTime i / frames; const animTime normalizedTime * animationClip.duration; if (tempMixer) { tempMixer.setTime(animTime); tempMixer.update(0); } if (i 0 || i frames - 1 || i % Math.floor(frames / 4) 0) { console.log(`Frame ${i}/${frames}: ${(normalizedTime * 100).toFixed(1)}% of animation (${animTime.toFixed(2)}s / ${animationClip.duration.toFixed(2)}s)`); } } } if (this.renderSettings.transparentBg) { tempRenderer.setClearColor(0x000000, 0); } else { tempRenderer.setClearColor(this.renderSettings.bgColor); } tempRenderer.render(this.scene, tempCamera); const dataURL tempRenderer.domElement.toDataURL(image/png); frameImages.push(dataURL); if (!isBatch) { const progress ((i + 1) / frames) * 70; this.showProgress(progress); this.showStatus(`Capturing animation frames... ${i + 1}/${frames} (${Math.round(progress)}%)`); } await new Promise(res > setTimeout(res, 10)); } console.log(`Frame capture complete: ${frameImages.length} frames captured for ${isTurntable ? 360° rotation : model animation}`); this.model.position.copy(originalPosition); this.model.rotation.copy(originalRotation); this.model.scale.copy(originalScale); if (tempMixer) { tempMixer.stopAllAction(); tempMixer null; } tempRenderer.dispose(); if (!isBatch) { this.showStatus(Encoding GIF animation... 70%); this.showProgress(70); } return new Promise((resolve) > { try { console.log(`Creating GIF with interval: ${frameInterval.toFixed(4)}s (${(frameInterval * 1000).toFixed(1)}ms) per frame`); gifshot.createGIF({ images: frameImages, gifWidth: resolution, gifHeight: resolution, interval: frameInterval, numFrames: frameImages.length, frameDuration: 1, sampleInterval: 10, numWorkers: 2 }, (obj) > { if (!obj.error) { if (!isBatch) { this.showProgress(95); this.showStatus(Finalizing GIF... 95%); } console.log(`GIF created successfully: ${frameImages.length} frames, ${duration}s duration, ${isTurntable ? 360° rotation : model animation}`); if (!isBatch) { const link document.createElement(a); const timestamp new Date().toISOString().replace(/:./g, -).slice(0, -5); const animType isTurntable ? turntable : (animationClip ? animationClip.name.replace(/^\w-/g, _) : animation); link.download `3d_${animType}_${timestamp}.gif`; link.href obj.image; document.body.appendChild(link); link.click(); document.body.removeChild(link); this.showProgress(100); const fps (frames / duration).toFixed(1); const typeText isTurntable ? turntable (360° rotation) : `model animation (${animationClip ? animationClip.name : unknown})`; this.showSuccess(`${typeText} GIF generated! (${frameImages.length} frames, ${duration}s, ~${fps} fps)`); this.hideProgress(); if (this.controls) this.controls.enabled true; document.getElementById(generate360Btn).disabled false; } resolve(obj.image); } else { console.error(GIF creation error:, obj.error, obj.errorMsg); if (!isBatch) { this.showError(Failed to create GIF animation: + (obj.errorMsg || obj.error)); this.hideProgress(); if (this.controls) this.controls.enabled true; document.getElementById(generate360Btn).disabled false; } resolve(null); } }); if (!isBatch) { let encodingProgress 70; const progressInterval setInterval(() > { if (encodingProgress 90) { encodingProgress + 2; this.showProgress(encodingProgress); this.showStatus(`Encoding GIF animation... ${encodingProgress}%`); } else { clearInterval(progressInterval); } }, 200); } } catch (error) { console.error(Error generating GIF:, error); if (!isBatch) { this.showError(Failed to generate GIF animation: + error.message); this.hideProgress(); if (this.controls) this.controls.enabled true; document.getElementById(generate360Btn).disabled false; } resolve(null); } }); } async processBatchAnimations() { this.showStatus(Starting batch animation generation...); document.getElementById(generateBtn).disabled true; document.getElementById(generate360Btn).disabled true; this.isBatchProcessing true; this.generatedGifs ; for (let i 0; i this.uploadedFiles.length; i++) { const file this.uploadedFilesi; this.showStatus(`Generating animation ${i + 1}/${this.uploadedFiles.length}: ${file.name}`); const success await this.processSingleFile(file, i); if (success) { this.updateFileStatus(i, processing, Animating...); const gifData await this.generateSingleAnimation(true); if (gifData) { this.generatedGifs.push({ name: `${this.currentModelName}_animation.gif`, data: gifData }); this.updateFileStatus(i, completed, GIF created); } else { this.updateFileStatus(i, error, GIF failed); } } await new Promise(res > setTimeout(res, 500)); } this.isBatchProcessing false; if (this.generatedGifs.length > 0) { this.showStatus(Creating ZIP file with all animations...); await this.downloadAllAnimations(); this.showSuccess(`Batch animation complete! ${this.generatedGifs.length} GIFs generated and downloaded as ZIP.`); } else { this.showError(No animations were generated.); } document.getElementById(generateBtn).disabled false; document.getElementById(generate360Btn).disabled false; } async downloadAllAnimations() { try { const zip new JSZip(); const folder zip.folder(animations); for (const gif of this.generatedGifs) { const base64Data gif.data.split(,)1; folder.file(gif.name, base64Data, { base64: true }); } const readmeContent `3D Model Animations CollectionTotal Animations: ${this.generatedGifs.length}Format: GIFAnimation Type: ${this.renderSettings.animationType turntable ? Turntable (360°) : Model Animation}Duration: ${this.renderSettings.animationDuration}sFrames: ${this.renderSettings.animationFrames}Generated: ${new Date().toLocaleString()}Files included:${this.generatedGifs.map(gif > - + gif.name).join(\n)}Generated by: 3D Model to Images ConverterWebsite: https://3d-model-to-images-converter.com/`; folder.file(README.txt, readmeContent); const content await zip.generateAsync({ type: blob, compression: DEFLATE, compressionOptions: { level: 6 } }); const url URL.createObjectURL(content); const link document.createElement(a); const timestamp new Date().getTime(); link.download `model_animations_${timestamp}.zip`; link.href url; document.body.appendChild(link); link.click(); document.body.removeChild(link); setTimeout(() > URL.revokeObjectURL(url), 100); } catch (error) { console.error(Error creating animation ZIP:, error); this.showError(Failed to create ZIP file: + error.message); } } getCameraForAngle(angle, baseCamera) { const tempCamera baseCamera.clone(); const target this.controls.target.clone(); const getRotation (axis, angle) > new THREE.Quaternion().setFromAxisAngle(axis, angle); const x_axis, y_axis new THREE.Vector3(1, 0, 0), new THREE.Vector3(0, 1, 0); const rotations { back: getRotation(y_axis, Math.PI), left: getRotation(y_axis, -Math.PI / 2), right: getRotation(y_axis, Math.PI / 2), top: getRotation(x_axis, -Math.PI / 2), bottom: getRotation(x_axis, Math.PI / 2), iso1: new THREE.Quaternion().setFromEuler(new THREE.Euler(-Math.PI / 6, Math.PI / 4, 0, YXZ)), iso2: new THREE.Quaternion().setFromEuler(new THREE.Euler(-Math.PI / 6, -Math.PI / 4, 0, YXZ)) }; const rotation rotationsangle; if (!rotation) return tempCamera; const cameraOffset baseCamera.position.clone().sub(target); cameraOffset.applyQuaternion(rotation); tempCamera.position.copy(target).add(cameraOffset); tempCamera.up.copy(baseCamera.up).applyQuaternion(rotation); tempCamera.lookAt(target); return tempCamera; } displayGeneratedImages() { const grid document.getElementById(imagesGrid); const notice document.getElementById(imagesGridNotice); if (this.generatedImages.length > 0) { notice.textContent `${this.generatedImages.length} images generated. Download individually or all at once. You can also generate a PDF catalog.`; notice.style.display block; // Clear and rebuild the entire grid to show all images grid.innerHTML ; this.generatedImages.forEach((image, index) > { const item document.createElement(div); item.className image-item; item.innerHTML ` img src${image.dataURL} alt${image.name}> h3>${image.name.replace(.png, ).replace(/_/g, )}/h3> button classbtn download-btn onclickconverter.downloadImage(${index})>💾 Download/button> `; grid.appendChild(item); }); } else { notice.style.display none; } document.getElementById(downloadAllBtn).style.display this.generatedImages.length > 0 ? flex : none; document.getElementById(generatePdfBtn).style.display this.generatedImages.length > 0 ? flex : none; } getAngleName(angle) { const names {front: Current,back: Back,left: Left,right: Right,top: Top,bottom: Bottom,iso1: Iso 1,iso2: Iso 2}; return namesangle || angle; } downloadImage(index) { const image this.generatedImagesindex; const link document.createElement(a); // Use a safe filename with timestamp const timestamp new Date().toISOString().replace(/:./g, -).slice(0, -5); link.download `${image.name.replace(.png, )}_${timestamp}.png`; link.href image.dataURL; document.body.appendChild(link); link.click(); document.body.removeChild(link); } async downloadAllImages() { if (this.generatedImages.length 0) return; this.showStatus(Creating ZIP file...); try { const zip new JSZip(); const folder zip.folder(model_images); console.log(`Creating ZIP with ${this.generatedImages.length} images`); // Add images to folder for (let i 0; i this.generatedImages.length; i++) { const image this.generatedImagesi; if (!image.dataURL || typeof image.dataURL ! string) { console.error(`Invalid image data at index ${i}:`, image); continue; } // Extract base64 data from data URL const matches image.dataURL.match(/^data:image\/png;base64,(.+)$/); if (!matches || !matches1) { console.error(`Invalid data URL format at index ${i}:`, image.dataURL.substring(0, 50)); continue; } const base64Data matches1; console.log(`Adding file: ${image.name} (${base64Data.length} chars)`); folder.file(image.name, base64Data, { base64: true }); } // Add README file with metadata const readmeContent `3D Model Images CollectionTotal Images: ${this.generatedImages.length}Resolution: ${this.renderSettings.resolution}×${this.renderSettings.resolution}pxFormat: PNGGenerated: ${new Date().toLocaleString()}Background: ${this.renderSettings.transparentBg ? Transparent : this.renderSettings.bgColor}Files included:${this.generatedImages.map(img > - + img.name).join(\n)}About this archive:- All images are in PNG format- Images are rendered from a 3D model- Safe to extract and useGenerated by: 3D Model to Images ConverterWebsite: https://3d-model-to-images-converter.com/`; folder.file(README.txt, readmeContent); console.log(Generating ZIP file...); // Generate ZIP with standard settings for better compatibility const content await zip.generateAsync({ type: blob, compression: STORE // No compression to avoid false positives }); console.log(`ZIP generated: ${content.size} bytes`); // Create download with proper MIME type const url URL.createObjectURL(content); const link document.createElement(a); const timestamp new Date().getTime(); link.download `model_images_${timestamp}.zip`; link.href url; document.body.appendChild(link); link.click(); document.body.removeChild(link); // Clean up after a delay setTimeout(() > { URL.revokeObjectURL(url); }, 100); this.showSuccess(this.t(zipDownloaded)); } catch (error) { console.error(Error creating ZIP:, error); this.showError(Failed to create ZIP file: + error.message); } } async generatePdfCatalog() { if (this.generatedImages.length 0) { this.showError(Please generate images first.); return; } this.showStatus(this.t(generatingPdf)); document.getElementById(generatePdfBtn).disabled true; const { jsPDF } window.jspdf; const pdf new jsPDF({ orientation: portrait, unit: mm, format: a4 }); const pageWidth pdf.internal.pageSize.getWidth(); const pageHeight pdf.internal.pageSize.getHeight(); const margin 20; const imgSize (pageWidth - margin * 3) / 2; pdf.setFontSize(20); pdf.text(3D Model Image Catalog, pageWidth / 2, margin, { align: center }); pdf.setFontSize(10); pdf.text(`Generated: ${new Date().toLocaleDateString()}`, pageWidth / 2, margin + 7, { align: center }); let yPos margin + 20; let xPos margin; let imagesOnPage 0; for (let i 0; i this.generatedImages.length; i++) { const image this.generatedImagesi; if (imagesOnPage 4) { pdf.addPage(); yPos margin; xPos margin; imagesOnPage 0; } pdf.addImage(image.dataURL, PNG, xPos, yPos, imgSize, imgSize); pdf.setFontSize(10); pdf.text(this.getAngleName(image.angle), xPos + imgSize / 2, yPos + imgSize + 5, { align: center }); imagesOnPage++; if (imagesOnPage % 2 0) { xPos margin; yPos + imgSize + 15; } else { xPos pageWidth / 2 + margin / 2; } await new Promise(res > setTimeout(res, 10)); } pdf.save(3d_model_catalog.pdf); this.showSuccess(PDF catalog generated and downloaded!); // Show info about Windows Security for PDF as well const securityInfo document.createElement(div); securityInfo.style.cssText margin-top: 15px; padding: 15px; background: rgba(255, 193, 7, 0.1); border-radius: 10px; border-left: 4px solid #ffc107; font-size: 0.9em;; securityInfo.innerHTML ` strong>ℹ️ Note:/strong> If Windows security flags this file, right-click → Properties → Unblock. The file is safe and contains only your generated images. `; const statusEl document.getElementById(status); if (!statusEl.querySelector(.security-info)) { securityInfo.className security-info; statusEl.appendChild(securityInfo); setTimeout(() > { if (securityInfo.parentNode) { securityInfo.remove(); } }, 12000); } document.getElementById(generatePdfBtn).disabled false; } showStatus(message) { const el document.getElementById(status); el.textContent message; el.className status; } showError(message) { const el document.getElementById(status); el.textContent message; el.className status error; } showSuccess(message) { const el document.getElementById(status); el.textContent message; el.className status success; } // Get translated message t(key) { const translations window.currentTranslations || {}; return translationskey || key; } showProgress(percent) { document.getElementById(progressBar).style.display block; document.getElementById(progressFill).style.width percent + %; } hideProgress() { setTimeout(() > { document.getElementById(progressBar).style.display none; }, 1000); } } const converter new Model3DConverter(); /script>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]