"use strict"
/**
* ...
* @namespace layout
*/
const layoutUtils = ({ lib, swLib }) => {
const { cuboid } = lib.primitives;
const { union, subtract } = lib.booleans;
const { translate, center, align } = lib.transforms;
const { measureDimensions, measureVolume } = lib.measurements;
const { text } = swLib.core
const { maths } = swLib.utils
const { frameCuboid } = swLib.utils.superPrimitives;
const layoutElements = new Map();
let largestDimensionX = 0;
let largestDimensionY = 0;
let sortedLayoutEntries = [];
const largestDimension = () => {
return [largestDimensionX, largestDimensionY];
}
const getXYArea = (dims) => {
return dims[0] * dims[1];
}
/**
* Comparator function for Array.sort()
* @param {number[]} firstEntry - [x,y,z] of first geometry
* @param {number[]} secondEntry - [x,y,z] of second geometry
*/
const dimsSizeAsc = (firstEntry, secondEntry) => {
const firstArea = getXYArea(firstEntry.layoutDims)
const secondArea = getXYArea(secondEntry.layoutDims)
if (firstArea.size > secondArea.size) {
return 1;
} else if (firstArea.size < secondArea.size) {
return -1;
} else {
return 0;
}
}
const addLayoutEntry = ({ layoutEntry }) => {
console.log(`addLayoutEntry() layoutEntry.id = ${JSON.stringify(layoutEntry.id)}, layoutEntry.desc = ${JSON.stringify(layoutEntry.desc)}`);
layoutElements.set(layoutEntry.id, layoutEntry);
const newEntries = [...sortedLayoutEntries, layoutEntry];
sortedLayoutEntries = newEntries.sort(dimsSizeAsc);
if (layoutEntry.layoutDims[0] > largestDimensionX) {
largestDimensionX = layoutEntry.layoutDims[0];
}
if (layoutEntry.layoutDims[1] > largestDimensionY) {
largestDimensionY = layoutEntry.layoutDims[1];
}
}
const layoutFrame = ({ title, subtitle = '. . .', data1 = '..', data2 = '....', objectDims, layoutDims }) => {
const frameWidth = 1.5;
const recessDepth = 0.6667;
const panelOpts = {
extrudeHeight: recessDepth,
panelThickness: frameWidth + recessDepth,
panelOffset: (frameWidth + recessDepth) * 2,
}
const titleText = text.textPanel({
message: title,
fontSize: 3.5,
charLineWidth: 1,
...panelOpts,
});
const subtitleText = text.textPanel({
message: subtitle,
fontSize: 3,
charLineWidth: 0.75,
...panelOpts,
});
const data1Text = text.textPanel({
message: data1,
fontSize: 3,
charLineWidth: 0.75,
...panelOpts,
});
const data2Text = text.textPanel({
message: data2,
fontSize: 3,
charLineWidth: 0.75,
...panelOpts,
});
const basicFrame = subtract(
cuboid({ size: [layoutDims[0], layoutDims[1], frameWidth] }),
cuboid({ size: [layoutDims[0] - (frameWidth * 2), layoutDims[1] - (frameWidth * 2), 3] }),
);
const ctrlPts = {
topLeft: [layoutDims[0] / -2, layoutDims[1] / 2],
topRight: [layoutDims[0] / 2, layoutDims[1] / 2],
bottomLeft: [layoutDims[0] / -2, layoutDims[1] / -2],
bottomRight: [layoutDims[0] / 2, layoutDims[1] / -2],
}
const alignmentSlots = {
topLeft: { modes: ['min', 'max', 'min'], relativeTo: [...ctrlPts.topLeft, 0] },
topRight: { modes: ['max', 'max', 'min'], relativeTo: [...ctrlPts.topRight, 0] },
bottomLeft: { modes: ['min', 'min', 'min'], relativeTo: [...ctrlPts.bottomLeft, 0] },
bottomRight: { modes: ['max', 'min', 'min'], relativeTo: [...ctrlPts.bottomRight, 0] },
}
return union(
align({ modes: ['center', 'center', 'min'] }, basicFrame),
align(alignmentSlots.topLeft, data1Text),
align(alignmentSlots.topRight, data2Text),
align(alignmentSlots.bottomLeft, titleText),
align(alignmentSlots.bottomRight, subtitleText),
);
}
const linearLayout = ({ layoutOpts }) => {
console.log(`linearLayout() layoutOpts = ${JSON.stringify(layoutOpts)}`);
const layoutContent = [];
layoutElements.values().forEach((val, idx) => {
// console.log(val, idx);
const offsets = { x: 0, y: 0, z: 0 };
if (layoutOpts.relativeTo) {
offsets.x = offsets.x + layoutOpts.relativeTo[0];
offsets.y = offsets.y + layoutOpts.relativeTo[1];
offsets.z = offsets.z + layoutOpts.relativeTo[2];
}
const gridUnits = [
largestDimension()[0] + layoutOpts.layoutSpace,
largestDimension()[1] + layoutOpts.layoutSpace,
]
let layoutPosition = [
gridUnits[0] * idx + offsets.x,
offsets.y,
offsets.z
];
if (layoutOpts.column) {
layoutPosition = [
offsets.x,
gridUnits[1] * idx + offsets.y,
offsets.z
];
}
const nextLayoutGeoms = [
translate(layoutPosition, val.geom),
]
console.log(` layoutPosition = ${JSON.stringify(layoutPosition)}`)
const skipFrame = layoutOpts.noFrame || val.tags.includes('noFrame');
console.log(` skipFrame = ${skipFrame}`)
if (!skipFrame) {
const frameGeom = translate(layoutPosition, layoutFrame({
title: val.name,
subtitle: val.desc,
objectDims: val.objectDims,
layoutDims: val.layoutDims,
}));
nextLayoutGeoms.push(frameGeom)
}
layoutContent.push(...nextLayoutGeoms)
})
return layoutContent;
}
const gridLayout = ({ layoutOpts }) => {
const gridSize = maths.factorize(layoutElements.size);
const numColumns = gridSize[1];
console.log(`gridLayout() layoutOpts = ${JSON.stringify(layoutOpts)}`);
console.log(` largestDimension = ${JSON.stringify(largestDimension())}`);
console.log(` gridSize = ${JSON.stringify(gridSize)}`);
const layoutContent = [];
let gridRow = -1;
layoutElements.values().forEach((val, idx) => {
const gridCol = idx % numColumns
if (gridCol === 0) {
gridRow += 1;
}
const gridPos = { row: gridRow, col: gridCol };
console.log(` gridPos = ${JSON.stringify(gridPos)}`);
const offsets = { x: 0, y: 0, z: 0 };
if (layoutOpts.relativeTo) {
offsets.x = offsets.x + layoutOpts.relativeTo[0];
offsets.y = offsets.y + layoutOpts.relativeTo[1];
offsets.z = offsets.z + layoutOpts.relativeTo[2];
}
const gridUnits = [
largestDimension()[0] + layoutOpts.layoutSpace,
largestDimension()[1] + layoutOpts.layoutSpace,
]
let layoutPosition = [
gridUnits[0] * gridPos.col + offsets.x,
gridUnits[1] * gridPos.row + offsets.y,
offsets.z
];
const nextLayoutGeoms = [
translate(layoutPosition, val.geom),
]
console.log(` layoutPosition = ${JSON.stringify(layoutPosition)}`)
const skipFrame = layoutOpts.noFrame || val.tags.includes('noFrame');
console.log(` skipFrame = ${skipFrame}`)
if (!skipFrame) {
const frameGeom = translate(layoutPosition, layoutFrame({
title: val.name,
subtitle: val.desc,
objectDims: val.objectDims,
layoutDims: val.layoutDims,
}));
nextLayoutGeoms.push(frameGeom)
}
layoutContent.push(...nextLayoutGeoms)
})
return layoutContent;
}
return {
/**
* Adds element to layout
* @memberof layout
* @instance
* @param {Object} opts
* @param {string} opts.name
* @param {string} opts.desc
* @param {string[]} opts.tags
* @param {Object} opts.geom
* @param {Object} opts.layoutOpts
* @param {number[]} opts.layoutOpts.minSize -- [x,y,z] showing minimum size for element layout
*/
addToLayout: ({ name = '', desc = '', tags = [], geom, layoutOpts = {} }) => {
console.log(`addToLayout() name = ${JSON.stringify(name)}, desc = ${JSON.stringify(desc)}`);
// console.log(` tags = ${JSON.stringify(tags)}, layoutOpts = ${JSON.stringify(layoutOpts)}`);
const layoutId = name + '-randomTag';
const objectDims = measureDimensions(geom);
const layoutMargin = layoutOpts.layoutMargin || 10;
const layoutDims = [
layoutMargin * 2 + objectDims[0],
layoutMargin * 2 + objectDims[1],
layoutMargin * 2 + objectDims[2],
];
if (!!layoutOpts.minSize) {
// use minimum sizes if necessary
console.log(` layoutOpts.minSize = ${JSON.stringify(layoutOpts.minSize)}`);
}
const layoutEntry = {
id: layoutId,
name,
desc,
tags,
geom: align({ modes: ['center', 'center', 'min'] }, geom),
objectDims,
layoutDims,
}
addLayoutEntry({ layoutEntry });
return layoutEntry;
},
removeFromLayout: ({ id }) => {
layoutElements.delete(id);
},
clearLayout: () => {
layoutElements.clear();
},
linearLayout,
gridLayout,
}
}
module.exports = { init: layoutUtils };