v0.7.8: light-versie in te bedden op elke pagina en eigen opslaan: texturen, kleurstalen en kosten/*.csv laden via tectumUrl() (js/core/geometry.js) vanaf de map van de scripts, zodat de engine niet naast de pagina hoeft te staan (modelpagina's van de site met de engine op /configurator/); paden in de data blijven relatief; TectumLight.mount(el, { save: { label, icon, onSave } }) vervangt de PDF-knop, onSave krijgt het ontwerp als house-design-JSON met kerncijfers, prijs en keuzes in woorden (ook el.tectumLight.design())
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b8cc39275b
commit
73248a69c9
@@ -4,7 +4,7 @@
|
||||
* Alle rechten voorbehouden. Gebruik uitsluitend met schriftelijke toestemming.
|
||||
* Zie LICENTIE.md.
|
||||
*/
|
||||
const APP_VERSION = '0.7.7';
|
||||
const APP_VERSION = '0.7.8';
|
||||
// 0.0.X per kleine iteratie, 0.X.0 per mijlpaal, X.0.0 bij live-gang.
|
||||
|
||||
let tabs = [];
|
||||
|
||||
@@ -6,6 +6,17 @@
|
||||
*/
|
||||
// Plain script, geen ES-module: imports breken bij file:// (CORS).
|
||||
|
||||
// Map van de engine (met assets/ en kosten/), afgeleid van de plek van dit script. Texturen, kleurstalen en de
|
||||
// kosten-CSV's laden via tectumUrl(), zodat ze ook werken als de engine in een andere pagina staat (light-versie op
|
||||
// een modelpagina van de site). Paden in de data blijven relatief.
|
||||
const TECTUM_BASE = (() => {
|
||||
const s = typeof document !== 'undefined' && document.currentScript && document.currentScript.src;
|
||||
return s ? new URL('../../', s).href : '';
|
||||
})();
|
||||
function tectumUrl(path) {
|
||||
return path && !/^(?:[a-z][a-z0-9+.-]*:|\/)/i.test(path) ? TECTUM_BASE + path : path;
|
||||
}
|
||||
|
||||
function calculateSegmentPositions(spanWidth, minSp, maxSp) {
|
||||
if (spanWidth <= 0) return [];
|
||||
let numBays = Math.ceil(spanWidth / maxSp);
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ window.Skybox = (() => {
|
||||
const cache = {};
|
||||
function loadTexture(url) {
|
||||
if (!cache[url]) {
|
||||
cache[url] = new Promise((resolve, reject) => new THREE.TextureLoader().load(url, resolve, undefined, reject));
|
||||
cache[url] = new Promise((resolve, reject) => new THREE.TextureLoader().load(tectumUrl(url), resolve, undefined, reject));
|
||||
cache[url].catch(() => { delete cache[url]; });
|
||||
}
|
||||
return cache[url];
|
||||
|
||||
+1
-1
@@ -114,7 +114,7 @@ window.CostCsv = (() => {
|
||||
}
|
||||
function loadList(list) {
|
||||
const url = LISTS[list].url;
|
||||
return fetch(url, { cache: 'no-store' })
|
||||
return fetch(tectumUrl(url), { cache: 'no-store' })
|
||||
.then(r => r.ok ? r.text().then(t => apply(list, t, 'server', url, r.headers.get('last-modified') || new Date().toISOString())) : null)
|
||||
.catch(() => null);
|
||||
}
|
||||
|
||||
@@ -293,7 +293,7 @@ window.FloorModel = (() => {
|
||||
}
|
||||
function finishThumbUrl(key, px) {
|
||||
const f = FINISHES[key];
|
||||
return f && f.file ? f.file.replace(/[^/]+$/, (n) => `thumb/${px || 64}/` + n.replace(/\.jpg$/, '.png')) : null;
|
||||
return f && f.file ? tectumUrl(f.file.replace(/[^/]+$/, (n) => `thumb/${px || 64}/` + n.replace(/\.jpg$/, '.png'))) : null;
|
||||
}
|
||||
const finishMaterials = {};
|
||||
function finishMaterial(key) {
|
||||
@@ -307,7 +307,7 @@ window.FloorModel = (() => {
|
||||
}
|
||||
const mat = new THREE.MeshStandardMaterial({ color: 0xb89a7c, roughness: 0.7 });
|
||||
if (typeof document !== 'undefined' && THREE.TextureLoader) {
|
||||
new THREE.TextureLoader().load(f.file, (tex) => {
|
||||
new THREE.TextureLoader().load(tectumUrl(f.file), (tex) => {
|
||||
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
|
||||
tex.repeat.set(1000 / f.tileMM, 1000 / f.tileMM);
|
||||
tex.anisotropy = 8;
|
||||
|
||||
+20
-2
@@ -8,8 +8,11 @@
|
||||
// Light-versie voor de modelpagina's: één model in 3D, een paar keuzes en de kerncijfers; opslaan = PDF met
|
||||
// aanzichten en plattegrond. Alle logica in LightModel (en daarachter de gewone modellen).
|
||||
// Inbedden: <div class="tectum-light" data-model="amstel" data-url="…"></div> (vanzelf gemount), of
|
||||
// TectumLight.mount(el, { model, frames, url, modelViewer }); url = adres voor de QR-code in de PDF (standaard de
|
||||
// TectumLight.mount(el, { model, frames, url, modelViewer, save }); url = adres voor de QR-code in de PDF (standaard de
|
||||
// modelpagina), modelViewer = adres van model-viewer (standaard js/vendor naast dit script).
|
||||
// save = { label, icon, onSave(ontwerp) } vervangt de PDF-knop (bijv. ontwerp aanvragen op de site); onSave krijgt
|
||||
// { design, label, key, info, choices, url }: design = house-design-JSON zoals de configurator die opslaat, choices = de
|
||||
// keuzes in woorden (zoals op het voorblad van de PDF). Zonder save blijft het de PDF.
|
||||
// Knop rechtsboven in de weergave: op een telefoon/tablet het ontwerp als GLB in model-viewer, met AR (WebXR, Scene
|
||||
// Viewer, Quick Look); op de desktop schermvullend.
|
||||
window.TectumLight = (() => {
|
||||
@@ -501,7 +504,8 @@ window.TectumLight = (() => {
|
||||
kv('Inhoud', `${nl(i.inhoudM3, 0)} m³`) + kv('Gewicht', `${nl(Math.round(i.kg / 10) * 10, 0)} kg`);
|
||||
$('.tl-buy').innerHTML = `<div class="tl-price"><span class="tl-price-val">${CostModel.eur(Math.round(i.price / 100) * 100)}</span><span class="tl-price-note">indicatief, incl. btw</span></div>
|
||||
<button type="button" class="tl-btn tl-icon" data-act="reset" title="Alle keuzes terug naar het model" aria-label="Terug naar het model"><i class="bi bi-arrow-counterclockwise"></i></button>
|
||||
<button type="button" class="tl-btn tl-primary" data-act="pdf"><i class="bi bi-file-earmark-pdf"></i>PDF</button>`;
|
||||
${o.save ? `<button type="button" class="tl-btn tl-primary" data-act="save"><i class="bi bi-${esc(o.save.icon || 'send')}"></i>${esc(o.save.label || 'Opslaan')}</button>`
|
||||
: '<button type="button" class="tl-btn tl-primary" data-act="pdf"><i class="bi bi-file-earmark-pdf"></i>PDF</button>'}`;
|
||||
}
|
||||
|
||||
// Keuzes (knoppen en kleuren).
|
||||
@@ -542,6 +546,7 @@ window.TectumLight = (() => {
|
||||
const a = e.target.closest('[data-act]');
|
||||
if (!a || !cur) return;
|
||||
if (a.dataset.act === 'pdf') savePdf();
|
||||
else if (a.dataset.act === 'save') o.save.onSave(design());
|
||||
else if (a.dataset.act === 'reset') tryApply(clone(model.initial));
|
||||
});
|
||||
panel.addEventListener('keydown', (e) => {
|
||||
@@ -608,6 +613,18 @@ window.TectumLight = (() => {
|
||||
}
|
||||
const pageUrl = o.url || root.dataset.url || LightPdf.modelUrl(model.key);
|
||||
|
||||
// Het huidige ontwerp voor buiten de tool (save.onSave, el.tectumLight.design()).
|
||||
function design() {
|
||||
const st = clone(cur.st);
|
||||
st.project.naam = model.label;
|
||||
st.project.opmerking = 'Ontwerp uit de light-versie';
|
||||
return {
|
||||
design: Object.assign({ type: 'house-design', version: 1, exportedAt: new Date().toISOString() }, st),
|
||||
label: model.label, key: model.key, info: Object.assign({}, cur.info),
|
||||
choices: LightModel.describe(model, ch, cur.st), url: pageUrl
|
||||
};
|
||||
}
|
||||
|
||||
function savePdf() {
|
||||
const st = clone(cur.st);
|
||||
st.project.naam = model.label;
|
||||
@@ -768,6 +785,7 @@ window.TectumLight = (() => {
|
||||
root.innerHTML = '';
|
||||
},
|
||||
getChoices: () => clone(ch),
|
||||
design: () => cur && design(),
|
||||
// GLB van het huidige ontwerp: { blob, url, stats } (zelfde als voor AR).
|
||||
glb: () => cur && makeGlb()
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ window.RoofModel = (() => {
|
||||
}
|
||||
function coveringThumbUrl(key, px) {
|
||||
const c = COVERINGS[key];
|
||||
return c && c.file ? c.file.replace(/[^/]+$/, (f) => `thumb/${px || 64}/` + f.replace(/\.jpg$/, '.png')) : null;
|
||||
return c && c.file ? tectumUrl(c.file.replace(/[^/]+$/, (f) => `thumb/${px || 64}/` + f.replace(/\.jpg$/, '.png'))) : null;
|
||||
}
|
||||
function coveringFor(key, shapeType) {
|
||||
const c = COVERINGS[key];
|
||||
@@ -108,7 +108,7 @@ window.RoofModel = (() => {
|
||||
if (!c.file) return null;
|
||||
const mat = new THREE.MeshStandardMaterial({ color: 0x9c8466, roughness: 0.95 });
|
||||
if (typeof document !== 'undefined' && THREE.TextureLoader) {
|
||||
new THREE.TextureLoader().load(c.file, (tex) => {
|
||||
new THREE.TextureLoader().load(tectumUrl(c.file), (tex) => {
|
||||
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
|
||||
tex.repeat.set(1000 / c.tileMM, 1000 / c.tileMM);
|
||||
tex.anisotropy = 8;
|
||||
|
||||
@@ -234,8 +234,8 @@ window.WallModel = (() => {
|
||||
'R961': '#c7b8a2', 'R970': '#ecdeb1', 'R980': '#ae9c88', 'R985': '#cd8b6d', 'R991': '#aa8274'
|
||||
};
|
||||
const isStoneFinish = (finish) => TRASRAAM_STONES.includes(finish);
|
||||
const stoneTextureUrl = (code) => STONE.DIR + encodeURIComponent(code + ' - Witte voeg.jpg');
|
||||
const stoneThumbUrl = (code, px) => STONE.DIR + 'thumb/' + (px || 64) + '/' + encodeURIComponent(code + ' - Witte voeg.png');
|
||||
const stoneTextureUrl = (code) => tectumUrl(STONE.DIR + encodeURIComponent(code + ' - Witte voeg.jpg'));
|
||||
const stoneThumbUrl = (code, px) => tectumUrl(STONE.DIR + 'thumb/' + (px || 64) + '/' + encodeURIComponent(code + ' - Witte voeg.png'));
|
||||
// UV's in meters, dus repeat = 1/tegel.
|
||||
const stoneMaterials = {};
|
||||
function stoneMaterial(code) {
|
||||
|
||||
Reference in New Issue
Block a user