Skip to content

Commit

Permalink
Diamond grid divider
Browse files Browse the repository at this point in the history
  • Loading branch information
justin-hackin committed Oct 16, 2021
1 parent 79ff83e commit 3bfb070
Show file tree
Hide file tree
Showing 11 changed files with 315 additions and 65 deletions.
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,17 +78,12 @@ An open polygon prism box with ring-shaped sleeve edges. This allows one to effi

This widget has a different UI configuration in the Assets accordion known as a "Disjunct Asset Definition" (as opposed to the "Registered Asset Definition" of the pyramid net). The different assets of the design can either be viewed all at once, or independently. This allows the user to see all changes to any widget parameter but also examine each component independently with less clicks than the per-asset visibility toggle.

#### Crosshatch Shelves
#### Grid dividers

<p alt="crosshatch shelf" align="center">
<img width="160px" src="/packages/renderer/static/images/widgets/crosshatch-shelf.jpg" />
<img width="160px" src="/packages/renderer/static/images/widgets/square-grid-divider.jpg" /><img width="160px" src="/packages/renderer/static/images/widgets/diamond-grid-divider.jpg" />
</p>
Enables the creation of slotted cross-hatched shelving inserts. Target application: spatial organization, storage of many bottles and/or cans within larger shelf section.

##### Components
- white grid is the profile view of the shelves
- green path is a horizontal shelf section
- red path is a vertical shelf section
Enables the creation of slotted cross-hatched shelving inserts. One widget creates square grids while the other creates "diamond" (45° rotated square) grid. Target application: spatial organization, storage of many bottles and/or cans within larger shelf section, collectibles box divider.

## Warning

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import { PyramidNetWidgetModel } from '../../widgets/PyramidNet/models/PyramidNe
import { radioProp } from '../../common/keystone-tweakables/props';
import { UNITS } from '../../common/util/units';
import { CylinderLightboxWidgetModel } from '../../widgets/CylinderLightbox/models';
import { CrosshatchShelvesWidgetModel } from '../../widgets/CrosshatchShelves/CrosshatchShelvesWidgetModel';
import { SquareGridDividerWidgetModel } from '../../widgets/CrosshatchShelves/SquareGridDividerWidgetModel';
import { BaseWidgetClass } from '../widget-types/BaseWidgetClass';
import { DiamondGridDividerWidgetModel } from '../../widgets/CrosshatchShelves/DiamondGridDividerWidgetModel';

// this assumes a file extension exists
const baseFileName = (fileName) => fileName.split('.').slice(0, -1).join('.');
Expand All @@ -39,7 +40,8 @@ const PREFERENCES_LOCALSTORE_NAME = 'WorkspacePreferencesModel';
const widgetOptions = {
'polyhedral-net': PyramidNetWidgetModel,
'cylinder-lightbox': CylinderLightboxWidgetModel,
'crosshatch-shelf': CrosshatchShelvesWidgetModel,
'square-grid-divider': SquareGridDividerWidgetModel,
'diamond-grid-divider': DiamondGridDividerWidgetModel,
};

const defaultWidgetName = 'polyhedral-net';
Expand Down
3 changes: 2 additions & 1 deletion packages/renderer/src/common/util/geom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,8 @@ export const distanceBetweenPoints = (pt1: PointLike, pt2: PointLike):number =>
subtractPoints(pt2, pt1),
);
export const isValidNumber = (num) => typeof num === 'number' && !isNaN(num);
const polygonWithFace = (faceVertices: PointLike[]) => {

export const polygonWithFace = (faceVertices: PointLike[]) => {
if (faceVertices.length < 3) {
throw new Error('polygonWithFace: face parameter must have 3 or more elements');
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import { ExtendedModel, model } from 'mobx-keystone';
import Flatten from '@flatten-js/core';
import { computed } from 'mobx';
import React from 'react';
import { round } from 'lodash';
import { DividerBaseSavedModel } from './DividerBaseSavedModel';
import { augmentSegmentEndpoints, getPositiveSlopeSlatSegments, notchPanel } from './util';
import { PathData } from '../../common/path/PathData';
import { getBoundingBoxAttrs } from '../../common/util/svg';
import { DisjunctWidgetAssetMember } from '../../WidgetWorkspace/widget-types/DisjunctAssetsDefinition';
import { switchProp } from '../../common/keystone-tweakables/props';
import Point = Flatten.Point;
import point = Flatten.point;
import segment = Flatten.segment;
import Segment = Flatten.Segment;

const reflectByWidth = (pt: Point, width: number) => (point(width - pt.x, pt.y));

interface SegmentInfo {
length: number,
copies: number,
firstIndex: number,
}

@model('DiamondGridDividerSavedModel')
export class DiamondGridDividerSavedModel extends ExtendedModel(DividerBaseSavedModel, {
flushPostProcess: switchProp(false),
}) {
@computed
get positiveCrosshatchSegments() {
return getPositiveSlopeSlatSegments(
this.shelfWidth.value, this.shelfHeight.value,
this.cubbyWidth.value, this.materialThickness.value,
).map((seg) => augmentSegmentEndpoints(seg, this.matThicknessAdjust));
}

@computed
get crosshatchProfilePath() {
return this.positiveCrosshatchSegments.reduce((path, segment) => path
.move(segment.ps).line(segment.pe)
.move(reflectByWidth(segment.ps, this.shelfWidth.value))
.line(reflectByWidth(segment.pe, this.shelfWidth.value)),
new PathData());
}

@computed
get uniqueSegmentsInfo(): SegmentInfo[] {
// could iterate over only half but this complicates case of centerd crosshatch
return this.positiveCrosshatchSegments
.map((segment) => round(segment.length, 10))
.reduce((acc, segLen, index) => {
// could be more efficient, meh
const lengthMatch = acc.find(({ length }) => length === segLen);
if (lengthMatch) {
lengthMatch.copies += 2;
} else {
acc.push({
length: segLen,
firstIndex: index,
copies: 2,
});
}
return acc;
}, [] as SegmentInfo[]);
}

@computed
get matThicknessAdjust(): number {
return ((this.flushPostProcess.value ? 1 : -1) * (this.materialThickness.value / 2));
}

getMirroredSegment(seg: Segment) {
const mirrorStart = reflectByWidth(seg.ps, this.shelfWidth.value);
const mirrorEnd = reflectByWidth(seg.pe, this.shelfWidth.value);
return segment(mirrorStart, mirrorEnd);
}

getFirstPanelNotchCenter(segIndex: number): number {
const targetSeg = this.positiveCrosshatchSegments[segIndex];
const firstIntersectionSegment = this.positiveCrosshatchSegments.find((seg) => {
const mirrorSegment = this.getMirroredSegment(seg);
const intersection = mirrorSegment.intersect(targetSeg);
return intersection.length > 0;
});
return targetSeg.intersect(this.getMirroredSegment(firstIntersectionSegment))[0].distanceTo(targetSeg.ps)[0];
}

getNumIntersectionsForPanel(segIndex: number) {
const targetSeg = this.positiveCrosshatchSegments[segIndex];
return this.positiveCrosshatchSegments
.reduce((numIntersects, seg) => (
this.getMirroredSegment(seg).intersect(targetSeg).length ? numIntersects + 1 : numIntersects
), 0);
}

@computed
get panelAssetMembers(): DisjunctWidgetAssetMember[] {
return this.uniqueSegmentsInfo.map(({ length, copies, firstIndex }, index) => {
const start = this.getFirstPanelNotchCenter(firstIndex);
const path = notchPanel(
start, length, this.shelfDepth.value,
this.getNumIntersectionsForPanel(firstIndex),
this.cubbyWidth.value, this.materialThickness.value,
);
const d = path.getD();
const { width, height } = getBoundingBoxAttrs(d);
return {
name: `Panel ${index + 1}`,
documentAreaProps: { width, height },
copies,
Component: () => (
<g>
<path d={d} fill="none" stroke="red" strokeWidth={4} />
</g>
),
};
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { ExtendedModel, model, prop } from 'mobx-keystone';
import { computed } from 'mobx';
import React from 'react';
import { BaseWidgetClass } from '../../WidgetWorkspace/widget-types/BaseWidgetClass';
import { DiamondGridDividerSavedModel } from './DiamondGridDividerSavedModel';
import { DisjunctAssetsDefinition } from '../../WidgetWorkspace/widget-types/DisjunctAssetsDefinition';

@model('DiamondGridDividerWidgetModel')
export class DiamondGridDividerWidgetModel extends ExtendedModel(BaseWidgetClass, {
savedModel: prop(() => new DiamondGridDividerSavedModel({})),
}) {
// eslint-disable-next-line class-methods-use-this
getFileBasename() {
return 'DiamondShelves';
}

specFileExtension = 'dsx';

@computed
get assetDefinition() {
return new DisjunctAssetsDefinition(
[
{
name: 'Profile view',
documentAreaProps: { width: this.savedModel.shelfWidth.value, height: this.savedModel.shelfHeight.value },
Component: () => (
<g>
<path
d={this.savedModel.crosshatchProfilePath.getD()}
fill="none"
stroke="#ddd"
strokeWidth={this.savedModel.materialThickness.value}
/>
</g>
),
},
...this.savedModel.panelAssetMembers,
], 0, true,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Model, model } from 'mobx-keystone';
import { numberTextProp } from '../../common/keystone-tweakables/props';
import { PIXELS_PER_INCH } from '../../common/util/units';

@model('DividerBaseSavedModel')
export class DividerBaseSavedModel extends Model({
shelfWidth: numberTextProp(96 * PIXELS_PER_INCH, {
useUnits: true,
}),
shelfHeight: numberTextProp(48 * PIXELS_PER_INCH, {
useUnits: true,
}),
shelfDepth: numberTextProp(24 * PIXELS_PER_INCH, {
useUnits: true,
}),
cubbyWidth: numberTextProp(10 * PIXELS_PER_INCH, {
useUnits: true,
}),
materialThickness: numberTextProp(0.5 * PIXELS_PER_INCH, {
useUnits: true,
}),
}) {
}
Original file line number Diff line number Diff line change
@@ -1,62 +1,20 @@
import { model, Model } from 'mobx-keystone';
import { ExtendedModel, model } from 'mobx-keystone';
import { computed } from 'mobx';
import { PathData } from '../../common/path/PathData';
import { numberTextProp, radioProp } from '../../common/keystone-tweakables/props';
import { PointTuple } from '../../common/util/geom';
import { radioProp } from '../../common/keystone-tweakables/props';
import { DividerBaseSavedModel } from './DividerBaseSavedModel';
import { getMarginLength, centeredNotchPanel } from './util';

enum REMAINDER_SIZES {
SMALL = 'small',
MEDIUM = 'medium',
LARGE = 'large',
}

const getMarginLength = (panelLength: number, numNotches: number, notchSpacing: number, notchThickness: number) => {
const notchesCoverage = (numNotches - 1) * notchSpacing + numNotches * notchThickness;
return (panelLength - notchesCoverage) / 2;
};

const PIXELS_PER_INCH = 96;
const notchedPanel = (
panelLength: number, panelDepth: number,
numNotches: number, notchSpacing: number, notchThickness: number, invertXY = false,
):PathData => {
const pt = (x:number, y: number) => (invertXY ? [y, x] : [x, y]) as PointTuple;
const path = new PathData();
const notchMargins = getMarginLength(panelLength, numNotches, notchSpacing, notchThickness);
const notchDepth = panelDepth / 2;
path.move([0, 0]);
for (let i = 0; i < numNotches; i += 1) {
const notchStartX = i * (notchThickness + notchSpacing) + notchMargins;
path.line(pt(notchStartX, 0))
.line(pt(notchStartX, notchDepth))
.line(pt(notchStartX + notchThickness, notchDepth))
.line(pt(notchStartX + notchThickness, 0));
}
return path
.line(pt(panelLength, 0))
.line(pt(panelLength, panelDepth))
.line(pt(0, panelDepth)).close();
};

const cubbiesDecrementOptions = Object.values(REMAINDER_SIZES).map((size, index) => ({ value: index, label: size }));

@model('CrosshatchShelvesSavedModel')
export class CrosshatchShelvesSavedModel extends Model({
shelfWidth: numberTextProp(96 * PIXELS_PER_INCH, {
useUnits: true,
}),
shelfHeight: numberTextProp(48 * PIXELS_PER_INCH, {
useUnits: true,
}),
shelfDepth: numberTextProp(24 * PIXELS_PER_INCH, {
useUnits: true,
}),
cubbyWidth: numberTextProp(8 * PIXELS_PER_INCH, {
useUnits: true,
}),
materialThickness: numberTextProp(0.5 * PIXELS_PER_INCH, {
useUnits: true,
}),
@model('SquareGridDividerSavedModel')
export class SquareGridDividerSavedModel extends ExtendedModel(DividerBaseSavedModel, {
widthCubbiesDecrement: radioProp(0, {
labelOverride: 'Left/right section size',
options: cubbiesDecrementOptions,
Expand Down Expand Up @@ -84,15 +42,15 @@ export class CrosshatchShelvesSavedModel extends Model({

@computed
get horizontalPanel() {
return notchedPanel(
return centeredNotchPanel(
this.shelfWidth.value, this.shelfDepth.value,
this.numCubbiesWide + 1, this.cubbyWidth.value, this.materialThickness.value,
);
}

@computed
get verticalPanel() {
return notchedPanel(
return centeredNotchPanel(
this.shelfHeight.value, this.shelfDepth.value,
this.numCubbiesHigh + 1, this.cubbyWidth.value, this.materialThickness.value, true,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import { ExtendedModel, model, prop } from 'mobx-keystone';
import React from 'react';
import { computed } from 'mobx';
import { BaseWidgetClass } from '../../WidgetWorkspace/widget-types/BaseWidgetClass';
import { CrosshatchShelvesSavedModel } from './CrosshatchShelvesSavedModel';
import { SquareGridDividerSavedModel } from './SquareGridDividerSavedModel';
import { DisjunctAssetsDefinition } from '../../WidgetWorkspace/widget-types/DisjunctAssetsDefinition';

@model('CrosshatchShelvesWidgetModel')
export class CrosshatchShelvesWidgetModel extends ExtendedModel(BaseWidgetClass, {
savedModel: prop<CrosshatchShelvesSavedModel>(() => new CrosshatchShelvesSavedModel({})),
@model('SquareGridDividerWidgetModel')
export class SquareGridDividerWidgetModel extends ExtendedModel(BaseWidgetClass, {
savedModel: prop<SquareGridDividerSavedModel>(() => new SquareGridDividerSavedModel({})),
}) {
specFileExtension = 'cxh';

Expand Down
Loading

0 comments on commit 3bfb070

Please sign in to comment.