Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
c37cc2f
feat(masonry): unify path and bounding box logic in generateBrickData
saumyashahi Jun 20, 2025
cd82bc3
test(masonry): update tests to match unified generateBrickData output
saumyashahi Jun 20, 2025
9647e9d
feat(masonry): add centroid calculation for top and bottom connection…
saumyashahi Jun 20, 2025
7c7fc54
feat(masonry): add centroid calculation for right and left connection…
saumyashahi Jun 20, 2025
6d41513
feat(masonry): include connection points in generateBrickData output
saumyashahi Jun 20, 2025
13fa25e
test(masonry): validate connection points in brick data output
saumyashahi Jun 20, 2025
eb14c9b
feat(masonry): add connectionPoints property to IBrick interface for …
saumyashahi Jun 21, 2025
457b361
feat(masonry): implement hierarchical brick connection logic for (top…
saumyashahi Jun 21, 2025
b75bba6
feat(masonry): update path generation utilities to support connection…
saumyashahi Jun 21, 2025
dfbd0f9
feat(masonry): update view components and stories to use new connecti…
saumyashahi Jun 21, 2025
684c0f6
feat(masonry): add BrickTreeManager for brick trees management with c…
saumyashahi Jun 21, 2025
9ad8c14
fix(masonry): add .js extension to BrickTreeManager import tests
saumyashahi Jun 21, 2025
4dcef61
chore(masonry): replace debug console.log statements with comments in…
saumyashahi Jun 22, 2025
9e2e50e
chore(masonry): fix all lint errors and warnings
saumyashahi Jun 22, 2025
ae77b0a
feat(masonry): add prop to extract bbox and connpoints
justin212407 Jun 22, 2025
60aca04
feat(Masonry): add public variables to store bbox ad conn points in b…
justin212407 Jun 22, 2025
65c7fad
docs(masonry): Add algorithm for parsing the tree
justin212407 Jun 22, 2025
4042477
"docs(masonry): Add algorithm for tree parsing"
justin212407 Jun 22, 2025
2f6e763
chore(masonry): fix doc linting errors
justin212407 Jun 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions modules/masonry/docs/technical-specification/Algorithm_Tree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
# Tower Parsing & Layout Algorithm

This document describes a **stack-based post-order traversal** that computes each brick’s SVG path,
bounding box, and notch‐connection points **after** all of its children have been measured. It
handles arbitrarily deep nesting, distinguishes **expression** vs. **simple** vs. **compound**
bricks, and avoids JavaScript call‐stack limits.

---

## Data Structures

```ts
// Represents one node in the tree (only UUID is needed here)
type TreeNode = {
uuid: string
}

// Holds the computed metrics for a brick
type Metrics = {
path: string
bbox: { w: number; h: number }
connectionPoints: ConnectionPoints
}

// Frame in our explicit stack
type Frame = {
node: TreeNode
visited: boolean
}

// Work‐in‐progress structures
const metricsMap: Map<string, Metrics> = new Map()
const stack: Frame[] = []
```

The model must expose:

```ts
getRoots(): string[]

getBrickType(uuid: string): 'expression' | 'simple' | 'compound'

getExpressionArgs(uuid: string): string[]

getNestedBlocks(uuid: string): string[]

// Optionally for simple/compound
type StatementChildren = { top: string; bottom: string }
getStatementChildren(uuid: string): StatementChildren

getBrickProps(uuid: string): {
label: string
type: 'expression' | 'simple' | 'compound'
strokeWidth: number
scale: number
topNotch: boolean
bottomNotch: boolean
fontSize: number
}
```

Shared utilities:

```ts
generatePath(config): string
getBoundingBox(config): { w: number; h: number }
deriveNotches(path, bbox): ConnectionPoints
measureLabel(text, fontSize): { w: number; h: number; ascent: number; descent: number }
```

---

## Algorithm Steps

1. **Initialize Work Structures**

- Empty metrics map

```pseudo
metricsMap ← {}
stack ← []
```

- Seed roots

```pseudo
for each rootUuid in model.getRoots():
stack.push({ node: { uuid: rootUuid }, visited: false })
```

We mark `visited = false` on first encounter (“children not yet handled”) and will re-push the
same node with `visited = true` (“ready to compute”) after its children.

2. **Process Frames Until Done**

```pseudo
while stack is not empty:
(currentNode, visited) ← stack.pop()

if visited == false:
// --- First encounter: enqueue children, defer parent ---
stack.push({ node: currentNode, visited: true })

// 1) Determine children based on brick type
type ← model.getBrickType(currentNode.uuid)
if type == 'expression':
children ← []
else if type == 'simple':
children ← model.getExpressionArgs(currentNode.uuid)
+ model.getStatementChildren(currentNode.uuid)
else if type == 'compound':
children ← model.getNestedBlocks(currentNode.uuid)
+ model.getExpressionArgs(currentNode.uuid)
+ model.getStatementChildren(currentNode.uuid)

// 2) Push children so they come off *before* the parent’s second visit
for each childUuid in reverse(children):
stack.push({ node: { uuid: childUuid }, visited: false })

else:
// --- Second encounter: all children are ready, compute metrics ---
// 1) Gather child bounding boxes
childBBoxes ← []
for each cUuid in model.getExpressionArgs(currentNode.uuid)
+ model.getNestedBlocks(currentNode.uuid)
+ model.getStatementChildren(currentNode.uuid):
childBBoxes.push(metricsMap[cUuid].bbox)

// 2) Fetch brick props and measure its label
props ← model.getBrickProps(currentNode.uuid)
labelBox ← measureLabel(props.label, props.fontSize)

// 3) Assemble config
config = {
type: props.type,
strokeWidth: props.strokeWidth,
scaleFactor: props.scale,
bBoxLabel: { w: labelBox.w, h: labelBox.h },
bBoxArgs: childBBoxes,
hasNotchAbove: props.topNotch,
hasNotchBelow: props.bottomNotch
}

// 4) Compute path, bbox, notch‐coords
path ← generatePath(config)
bbox ← getBoundingBox(config)
connectionPoints ← deriveNotches(path, bbox)

// 5) Store into metricsMap
metricsMap[currentNode.uuid] = { path, bbox, connectionPoints }
```

**Key invariant**: When a node’s frame is popped with `visited = true`, all of its children (and
their entire subtrees) have already been computed and stored in `metricsMap`.

3. **Completion**

When the stack empties, `metricsMap` contains the final layout data for every brick:

- SVG outline (`path`)
- Dimensions (`bbox`)
- Notch coordinates (`connectionPoints`)

You can now feed these into your React components or canvas renderer in a single, child‐first
batch.

---

## Why This Approach

- **Post‐order traversal** ensures each statement/compound block sizes itself around fully measured
plugged‐in bricks.
- **Explicit stack** avoids recursion limits—safe for arbitrary nesting depth.
- **Type‐aware child selection** respects the two “directions” of plug‐ins:
- Expression bricks never act as parents (no children).
- Simple bricks only host expression‐slot arguments (and top/bottom chain).
- Compound bricks first nest entire inner‐blocks, then expression args, then statement chaining.
- **No extra bookkeeping**: the “two‐push visited‐flag” trick implicitly tracks when children are
done without counters or complex state.
3 changes: 3 additions & 0 deletions modules/masonry/src/brick/@types/brick.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export type TBrickRenderPropsCompound = TBrickRenderProps & {
isFolded: boolean;
};

import { TConnectionPoints } from '../../tree/model/model';

/**
* @interface
* Type definition of a brick (any type).
Expand All @@ -75,6 +77,7 @@ export interface IBrick {
get type(): TBrickType;
set scale(value: number);
get boundingBox(): TExtent;
connectionPoints: TConnectionPoints;

get visualState(): TVisualState;
set visualState(value: TVisualState);
Expand Down
Loading