'); 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 = '
'; 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 '
'; }).join(''); var fk = Object.keys(furn).sort(function (a, b) { return a.localeCompare(b, 'ru'); }); var ftr = fk.map(function (k) { return '
'; }).join(''); return '
' + '
| Поз. | Деталь | Размер, мм | Толщ. | Материал | Кромка | Кол. |
|---|
| Наименование | Кол. |
|---|
Материалы и цвета - как в проекте. Перед изготовлением сверьте размеры с замером помещения.