3D просмотр модели Базиса
Перетащите сюда файл модели .b3d или нажмите, чтобы выбрать. Файл не отправляется в интернет - модель открывается прямо в вашем браузере. Что внутри: фасады и «без фасадов», разнесение, линейка, материалы, спецификация деталей, отверстия, режимы «Клиент» и «Цех», проверка прогиба полок (кнопка «Полки»), размеры блоков, надписи на деталях («ЛИЦО», «ВЕРХ»...). Готовую страницу можно скачать и отправить клиенту - она откроется на любом телефоне.
'); if (i < 0 || j < 0) return html; html = html.slice(0, i) + EXPOSE_AT + ' window.V3X = { THREE: THREE, scene: scene, camera: camera, canvas: canvas, items: items, render: render, $: $, opn: function () { return OPN; } };' + html.slice(i + EXPOSE_AT.length); j = html.lastIndexOf(' '); return html.slice(0, j) + ' '); return html.slice(0, j) + card + html.slice(j); } function clientDialog(price) { if (!lastRes) return; var c = getContact(), ov = document.createElement('div'); ov.className = 'cl-ov'; ov.innerHTML = '

Страница для клиента

Заказчик увидит модель в 3D (двери и ящики открываются), цену и ваши контакты. ' + 'Отверстий, файлов и цеховых данных на странице нет. Файл откроется на любом телефоне - отправьте его в Telegram или по почте.
' + '' + '' + '
' + '
'; document.body.appendChild(ov); $('clT').value = lastTitle; $('clP').value = price || c.price || ''; $('clN').value = c.note || ''; $('clName').value = c.name || ''; $('clPh').value = c.phone || ''; $('clTg').value = c.tg || ''; function opts() { var o = { title: $('clT').value.trim(), price: $('clP').value.trim(), note: $('clN').value.trim(), name: $('clName').value.trim(), phone: $('clPh').value.trim(), tg: $('clTg').value.trim() }; try { localStorage.setItem('mebelsam.client.contact', JSON.stringify({ name: o.name, phone: o.phone, tg: o.tg, note: o.note })); } catch (e) { } return o; } function url(o) { var u = URL.createObjectURL(new Blob([makeClient(o)], { type: 'text/html' })); setTimeout(function () { URL.revokeObjectURL(u); }, 60000); return u; } $('clCancel').onclick = function () { ov.remove(); }; $('clView').onclick = function () { window.open(url(opts()), '_blank'); }; $('clSave').onclick = function () { var o = opts(), a = document.createElement('a'); a.href = url(o); a.download = (o.title || lastTitle) + ' - для клиента.html'; document.body.appendChild(a); a.click(); a.remove(); ov.remove(); }; window.V3_CLIENT = makeClient; // для проверки } var lastUrl = '', lastName = '', lastHtml = '', lastLabels = []; // надписи присылает окно просмотра (модуль надписей) после каждой правки window.addEventListener('message', function (e) { var f = $('v3Stage').querySelector('iframe'); if (f && e.source === f.contentWindow && e.data && Array.isArray(e.data.v3labels)) window.V3_LABELS = lastLabels = e.data.v3labels; }); function show(res, title) { if (lastUrl) URL.revokeObjectURL(lastUrl); lastHtml = res.html; lastLabels = []; lastUrl = URL.createObjectURL(new Blob([res.html], { type: 'text/html' })); lastName = title + ' - 3D.html'; var stage = $('v3Stage'); stage.innerHTML = ''; $('v3Title').textContent = title; lastRes = res; lastTitle = title; $('v3Open').hidden = $('v3Save').hidden = $('v3Client').hidden = false; $('v3New').textContent = 'Другая модель…'; } function paywall() { var stage = $('v3Stage'); stage.innerHTML = '
Бесплатные просмотры закончились' + '

Вы открыли ' + FREE + ' модели бесплатно. Чтобы продолжить - оформите доступ, мы пришлём код.

' + '

Уже есть код доступа?

' + '' + '
'; $('v3Buy').onclick = function () { if (S.buyForm) S.buyForm('Доступ к онлайн 3D просмотру'); }; $('v3Apply').onclick = function () { var h = codeHash($('v3Code').value); if (CODES.indexOf(h) < 0) { $('v3Err').textContent = 'Код не подходит. Проверьте и попробуйте ещё раз.'; $('v3Err').hidden = false; return; } put('code', h); showLeft(); resetStage(); }; } function resetStage() { $('v3Stage').innerHTML = ''; var d = document.createElement('div'); d.className = 'v3-drop'; d.id = 'v3Drop'; d.innerHTML = 'Перетащите сюда файл модели .b3dили нажмите, чтобы выбрать'; d.onclick = pick; $('v3Stage').appendChild(d); } function pick() { if (!hasAccess() && !left()) { paywall(); return; } $('v3File').click(); } function open(file) { if (!/\.b3d$/i.test(file.name)) { alert('Нужен файл модели Базиса (.b3d).'); return; } if (!hasAccess() && !left()) { paywall(); return; } var busy = document.createElement('div'); busy.className = 'v3-busy'; busy.textContent = 'Открываю «' + file.name + '»…'; $('v3Stage').appendChild(busy); file.arrayBuffer().then(function (buf) { return new Promise(function (ok) { setTimeout(ok, 30); }).then(function () { return S.readModel(buf, file.name); }).then(function (model) { return prefetchDecor(model).then(function () { return model; }); }).then(function (model) { var res = run(model, file.name); if (!res.parts) throw new Error('в модели не найдено деталей'); if (!hasAccess()) put('used', String(used() + 1)); showLeft(); window.V3_LAST = res; // для проверки show(res, file.name.replace(/\.b3d$/i, '')); if (S.hub) S.hub.say('viewer', 'деталей ' + res.parts, { parts: res.parts }); }); })['catch'](function (e) { busy.remove(); alert('Не получилось открыть модель: ' + (e && e.message || e)); }); } $('v3File').onchange = function () { if (this.files[0]) open(this.files[0]); this.value = ''; }; $('v3New').onclick = pick; $('v3Drop').onclick = pick; $('v3Open').onclick = function () { if (lastUrl) window.open(lastUrl, '_blank'); }; $('v3Save').onclick = function () { // в скачанную страницу - и надписи, поставленные сейчас var html = lastHtml, url = lastUrl; try { var L = lastLabels; if (L.length) { var js = 'window.V3_LABELS_INIT = ' + JSON.stringify(L).replace(/ designer.accessCodes (в кавычках):', codeHash(c)); } $('v3Client').onclick = function () { clientDialog(''); }; // «Полный отчёт» просит страницу для клиента - с ценой из сметы window.addEventListener('message', function (e) { if (e.source === window.parent && e.data && e.data.mebelsam === 'client') clientDialog(e.data.price || ''); }); if (S.hub) S.hub.listen('viewer', open); // ---- Выдача оплаченного проекта (?room=1&deliver=1): комната -> Project для цеха, 3D-страница, спецификация (печать в PDF) ---- // Файл .b3d клиенту НЕ выдаётся (Андрей 27.10). checkOnly - только проверка модели (для текста заявки). // Ответ хозяину: {mebelsam:'delivered', files:[{name, type, data: Blob}], check} или {mebelsam:'deliverError', error}. function loadJs(src) { return new Promise(function (ok, bad) { if (document.querySelector('script[data-src="' + src + '"]')) { ok(); return; } var s = document.createElement('script'); s.src = src; s.setAttribute('data-src', src); s.onload = function () { ok(); }; s.onerror = function () { bad(new Error('не загрузился ' + src)); }; document.head.appendChild(s); }); } function checkModel(model) { // наша «ПРОВЕРКА ОБЩАЯ» (MC): ошибки (уровень 0) и замечания var Sn = MC.snapshot(model, false), res = MC.run(Sn, {}, {}), err = res.filter(function (r) { return r.level === 0; }); return { ok: !err.length, errors: err.length, notes: res.length - err.length, panels: Sn.panels.length, snap: Sn, text: err.length ? '⚠️ ошибок ' + err.length + ': ' + err.slice(0, 3).map(function (r) { return r.text; }).join('; ') : (res.length ? '✅ ошибок нет, замечаний ' + res.length : '✅ ошибок нет') }; } function specHtml(model, title, Sn) { // спецификация: детали (по позициям) и фурнитура - страница для печати / «Сохранить как PDF» var parts = {}, furn = {}; Sn.panels.forEach(function (p) { var L = Math.round(Math.max(p.w, p.h)), W = Math.round(Math.min(p.w, p.h)), e = p.elems || []; var ed = e.filter(function (x) { return x.butt; }), et = {}; ed.forEach(function (x) { et[x.butt] = 1; }); var edge = ed.length ? ed.length + ' ст. ' + Object.keys(et).join('/') + ' мм' : '-'; var k = [p.pos || '', p.name, L, W, Math.round(p.ath * 10) / 10, p.mat, edge].join('|'); parts[k] = (parts[k] || 0) + 1; }); (function walk(o) { var n = 0; try { n = Number(o.Count) || 0; } catch (x) { } for (var i = 0; i < n; i++) { var c = null; try { c = o[i] || (o.Objects && o.Objects[i]); } catch (x) { } if (!c) continue; var k = ''; try { k = String(c.constructor && c.constructor.name || ''); } catch (x) { } if (k === 'TFastener' || k === 'TFurnAsm' || k === 'TAsmKit') { var fn = String(c.Name || '').trim(); if (fn && fn !== 'Габарит секции') furn[fn] = (furn[fn] || 0) + 1; continue; } if (k === 'TFurnPanel' || k === 'TModelLimits' || /Size/.test(k)) continue; walk(c); } })(model); var rows = Object.keys(parts).sort(function (a, b) { return String(a.split('|')[0]).localeCompare(String(b.split('|')[0]), 'ru', { numeric: true }); }), total = 0; var trs = rows.map(function (k) { var f = k.split('|'); total += parts[k]; return '' + esc(f[0]) + '' + esc(f[1]) + '' + f[2] + ' × ' + f[3] + '' + f[4] + '' + esc(f[5]) + '' + esc(f[6]) + '' + parts[k] + ''; }).join(''); var fk = Object.keys(furn).sort(function (a, b) { return a.localeCompare(b, 'ru'); }); var ftr = fk.map(function (k) { return '' + esc(k) + '' + furn[k] + ''; }).join(''); return '' + esc(title) + ' - спецификация' + ' ' + '

' + esc(title) + '

Спецификация · ' + new Date().toLocaleDateString('ru-RU') + ' · МебельСам
' + '

Детали (' + total + ' шт.)

' + trs + '
Поз.ДетальРазмер, ммТолщ.МатериалКромкаКол.
' + (ftr ? '

Фурнитура

' + ftr + '
НаименованиеКол.
' : '') + '

Материалы и цвета - как в проекте. Перед изготовлением сверьте размеры с замером помещения.

'; } function deliver(e) { var d = e.data, src = e.source || host, title = d.name || 'Кухня', nm = title + '.b3d'; function reply(m) { try { src.postMessage(m, '*'); } catch (x) { } } if (!d.checkOnly && !d.token) { reply({ mebelsam: 'deliverError', error: 'проект ещё не оплачен' }); return; } Promise.all([loadJs('../js/mc-3.0.js'), loadJs('prj-3.0.js'), loadJs('../js/num-3.0.js')]).then(function () { var model = EMU.build(d.f, nm); // номера деталей (позиции) - наша «Нумерация» с настройками по умолчанию: в комнатах сайта детали без номеров, // а цеху (Project, спецификация) номера нужны try { NUMENV(model, { order: 0, fmt: 0, mode: 0, start: 1, tol: '', skip: null }); } catch (x) { } var ck = checkModel(model); var check = { ok: ck.ok, errors: ck.errors, notes: ck.notes, text: ck.text }; if (d.checkOnly) { reply({ mebelsam: 'delivered', files: [], check: check }); return; } return prefetchDecor(model).then(function () { var res = run(model, nm); if (!res.parts) throw new Error('в комнате нет блоков'); var opt = {}, k; for (k in PRJ.DEFAULTS) opt[k] = PRJ.DEFAULTS[k]; var pr = PRJ.exportProject(model, opt, {}, title, false); if (!pr.count) throw new Error('Project: не найдено деталей'); var files = [ { name: title + '.project', type: 'application/octet-stream', data: new Blob([PRJ.to1251(pr.xml)], { type: 'application/octet-stream' }) }, { name: title + ' - 3D и сборка.html', type: 'text/html', data: new Blob([res.html], { type: 'text/html' }) }, { name: title + ' - спецификация.html', type: 'text/html', data: new Blob([specHtml(model, title, ck.snap)], { type: 'text/html' }) } ]; reply({ mebelsam: 'delivered', files: files, check: check, parts: pr.count }); }); })['catch'](function (x) { reply({ mebelsam: 'deliverError', error: String(x && x.message || x) }); }); } // ---- Из комнаты сайта (кнопка «3D просмотр» в кухне/спальне): ?room=1 - модель комнаты приходит сообщением ---- // (без файла .b3d и без счёта бесплатных просмотров - это просмотр своего проекта) if (/[?&]room=1(&|$)/.test(location.search)) { var host = window.parent !== window ? window.parent : window.opener; window.addEventListener('message', function (e) { if (!e.data || e.data.mebelsam !== 'roomModel' || !e.data.f) return; if (e.data.deliver) { deliver(e); return; } var nm = (e.data.name || 'Комната') + '.b3d', fail = function (x) { $('v3Stage').innerHTML = '
Не получилось показать комнату' + esc(x && x.message || x) + '
'; }; try { var model = EMU.build(e.data.f, nm); prefetchDecor(model).then(function () { try { var res = run(model, nm); if (!res.parts) throw new Error('в комнате пока нет блоков'); window.V3_LAST = res; show(res, e.data.name || 'Комната'); $('v3New').hidden = true; } catch (x) { fail(x); } }); } catch (x) { fail(x); } }); var hdr = document.querySelector('header.top'); if (hdr) hdr.style.display = 'none'; // внутри комнаты - без шапки сайта $('v3Stage').innerHTML = '
Готовлю 3D просмотр комнаты…
'; $('v3Left').textContent = ''; if (host) host.postMessage({ mebelsam: 'roomReady' }, '*'); } else showLeft(); window.V3 = { open: open, codeHash: codeHash, client: clientDialog }; // для проверки })();