@tscircuit/rectdiff 0.0.33 → 0.0.34
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/SolverDebugger3d.tsx +64 -10
- package/lib/fixtures/twoNodeExpansionFixture.ts +1 -1
- package/lib/solvers/GapFillSolver/ExpandEdgesToEmptySpaceSolver.ts +2 -2
- package/lib/solvers/GapFillSolver/FindSegmentsWithAdjacentEmptySpaceSolver.ts +1 -1
- package/lib/solvers/GapFillSolver/GapFillSolverPipeline.ts +2 -2
- package/lib/solvers/RectDiffExpansionSolver/RectDiffExpansionSolver.ts +5 -2
- package/lib/solvers/RectDiffGridSolverPipeline/RectDiffGridSolverPipeline.ts +8 -5
- package/lib/solvers/RectDiffGridSolverPipeline/buildObstacleIndexes.ts +6 -6
- package/lib/solvers/RectDiffSeedingSolver/RectDiffSeedingSolver.ts +4 -4
- package/lib/solvers/RectDiffSeedingSolver/computeCandidates3D.ts +1 -1
- package/lib/solvers/RectDiffSeedingSolver/computeEdgeCandidates3D.ts +1 -1
- package/lib/solvers/RectDiffSeedingSolver/longestFreeSpanAroundZ.ts +1 -1
- package/lib/types/capacity-mesh-types.ts +1 -1
- package/lib/utils/expandRectFromSeed.ts +1 -1
- package/lib/utils/finalizeRects.ts +1 -1
- package/lib/utils/isFullyOccupiedAtPoint.ts +1 -1
- package/lib/utils/isSelfRect.ts +1 -1
- package/lib/utils/rectToTree.ts +2 -2
- package/lib/utils/renderObstacleClearance.ts +1 -1
- package/lib/utils/resizeSoftOverlaps.ts +1 -1
- package/lib/utils/sameTreeRect.ts +1 -1
- package/lib/utils/searchStrip.ts +1 -1
- package/lib/utils/z-filter.ts +43 -0
- package/package.json +6 -2
- package/tests/z-filter.test.ts +33 -0
- package/dist/index.d.ts +0 -381
- package/dist/index.js +0 -2764
|
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
|
|
2
2
|
import { GenericSolverDebugger } from "@tscircuit/solver-utils/react"
|
|
3
3
|
import type { SimpleRouteJson } from "../lib/types/srj-types"
|
|
4
4
|
import type { CapacityMeshNode } from "../lib/types/capacity-mesh-types"
|
|
5
|
+
import { matchesExactZFilter, parseZFilterInput } from "../lib/utils/z-filter"
|
|
5
6
|
import * as THREE from "three"
|
|
6
7
|
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"
|
|
7
8
|
import type { BaseSolver } from "@tscircuit/solver-utils"
|
|
@@ -168,6 +169,7 @@ const ThreeBoardView: React.FC<{
|
|
|
168
169
|
shrinkBoxes: boolean
|
|
169
170
|
boxShrinkAmount: number
|
|
170
171
|
showBorders: boolean
|
|
172
|
+
selectedZValues: number[] | null
|
|
171
173
|
}> = ({
|
|
172
174
|
nodes,
|
|
173
175
|
srj,
|
|
@@ -181,6 +183,7 @@ const ThreeBoardView: React.FC<{
|
|
|
181
183
|
shrinkBoxes,
|
|
182
184
|
boxShrinkAmount,
|
|
183
185
|
showBorders,
|
|
186
|
+
selectedZValues,
|
|
184
187
|
}) => {
|
|
185
188
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
186
189
|
const destroyRef = useRef<() => void>(() => {})
|
|
@@ -203,9 +206,17 @@ const ThreeBoardView: React.FC<{
|
|
|
203
206
|
|
|
204
207
|
const layerCount = layerNames.length || srj?.layerCount || 1
|
|
205
208
|
|
|
206
|
-
const
|
|
207
|
-
() =>
|
|
208
|
-
|
|
209
|
+
const filteredNodes = useMemo(
|
|
210
|
+
() =>
|
|
211
|
+
nodes.filter((node) =>
|
|
212
|
+
matchesExactZFilter(node.availableZ ?? [], selectedZValues),
|
|
213
|
+
),
|
|
214
|
+
[nodes, selectedZValues],
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
const filteredPrisms = useMemo(
|
|
218
|
+
() => buildPrismsFromNodes(filteredNodes, layerCount),
|
|
219
|
+
[filteredNodes, layerCount],
|
|
209
220
|
)
|
|
210
221
|
|
|
211
222
|
useEffect(() => {
|
|
@@ -372,6 +383,8 @@ const ThreeBoardView: React.FC<{
|
|
|
372
383
|
.map((name) => zIndexByLayerName.get(name))
|
|
373
384
|
.filter((z): z is number => typeof z === "number")
|
|
374
385
|
|
|
386
|
+
if (!matchesExactZFilter(zs, selectedZValues)) continue
|
|
387
|
+
|
|
375
388
|
for (const z of zs) {
|
|
376
389
|
if (z < 0 || z >= layerCount) continue
|
|
377
390
|
obstaclesGroup.add(
|
|
@@ -389,7 +402,7 @@ const ThreeBoardView: React.FC<{
|
|
|
389
402
|
|
|
390
403
|
// Output prisms from nodes (wireframe toggle like the experiment)
|
|
391
404
|
if (showOutput) {
|
|
392
|
-
for (const p of
|
|
405
|
+
for (const p of filteredPrisms) {
|
|
393
406
|
let box = p
|
|
394
407
|
if (shrinkBoxes && boxShrinkAmount > 0) {
|
|
395
408
|
const s = boxShrinkAmount
|
|
@@ -442,7 +455,7 @@ const ThreeBoardView: React.FC<{
|
|
|
442
455
|
z1: layerCount,
|
|
443
456
|
}
|
|
444
457
|
: (() => {
|
|
445
|
-
if (
|
|
458
|
+
if (filteredPrisms.length === 0) {
|
|
446
459
|
return {
|
|
447
460
|
minX: -10,
|
|
448
461
|
maxX: 10,
|
|
@@ -456,7 +469,7 @@ const ThreeBoardView: React.FC<{
|
|
|
456
469
|
minY = Infinity,
|
|
457
470
|
maxX = -Infinity,
|
|
458
471
|
maxY = -Infinity
|
|
459
|
-
for (const p of
|
|
472
|
+
for (const p of filteredPrisms) {
|
|
460
473
|
minX = Math.min(minX, p.minX)
|
|
461
474
|
maxX = Math.max(maxX, p.maxX)
|
|
462
475
|
minY = Math.min(minY, p.minY)
|
|
@@ -517,7 +530,7 @@ const ThreeBoardView: React.FC<{
|
|
|
517
530
|
}
|
|
518
531
|
}, [
|
|
519
532
|
srj,
|
|
520
|
-
|
|
533
|
+
filteredPrisms,
|
|
521
534
|
layerCount,
|
|
522
535
|
layerThickness,
|
|
523
536
|
height,
|
|
@@ -530,6 +543,7 @@ const ThreeBoardView: React.FC<{
|
|
|
530
543
|
shrinkBoxes,
|
|
531
544
|
boxShrinkAmount,
|
|
532
545
|
showBorders,
|
|
546
|
+
selectedZValues,
|
|
533
547
|
])
|
|
534
548
|
|
|
535
549
|
return (
|
|
@@ -572,10 +586,18 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
572
586
|
const [shrinkBoxes, setShrinkBoxes] = useState(true)
|
|
573
587
|
const [boxShrinkAmount, setBoxShrinkAmount] = useState(0.1)
|
|
574
588
|
const [showBorders, setShowBorders] = useState(true)
|
|
589
|
+
const [zFilterInput, setZFilterInput] = useState("")
|
|
575
590
|
|
|
576
591
|
// Mesh nodes state - updated when solver completes or during stepping
|
|
577
592
|
const [meshNodes, setMeshNodes] = useState<CapacityMeshNode[]>([])
|
|
578
593
|
|
|
594
|
+
const selectedZValues = useMemo(
|
|
595
|
+
() => parseZFilterInput(zFilterInput),
|
|
596
|
+
[zFilterInput],
|
|
597
|
+
)
|
|
598
|
+
const hasInvalidZFilter =
|
|
599
|
+
zFilterInput.trim().length > 0 && selectedZValues === null
|
|
600
|
+
|
|
579
601
|
// Update mesh nodes from solver output
|
|
580
602
|
const updateMeshNodes = useCallback(() => {
|
|
581
603
|
try {
|
|
@@ -601,11 +623,16 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
601
623
|
// Poll for updates during stepping (GenericSolverDebugger doesn't have onStep)
|
|
602
624
|
useEffect(() => {
|
|
603
625
|
const interval = setInterval(() => {
|
|
604
|
-
|
|
605
|
-
if (solver.solved || solver.stats?.placed > 0) {
|
|
626
|
+
if (solver.solved) {
|
|
606
627
|
updateMeshNodes()
|
|
628
|
+
clearInterval(interval)
|
|
629
|
+
return
|
|
607
630
|
}
|
|
608
|
-
|
|
631
|
+
|
|
632
|
+
if (solver.stats?.placed > 0) {
|
|
633
|
+
updateMeshNodes()
|
|
634
|
+
}
|
|
635
|
+
}, 100)
|
|
609
636
|
|
|
610
637
|
return () => clearInterval(interval)
|
|
611
638
|
}, [updateMeshNodes, solver])
|
|
@@ -724,6 +751,32 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
724
751
|
/>
|
|
725
752
|
Wireframe
|
|
726
753
|
</label>
|
|
754
|
+
|
|
755
|
+
<label
|
|
756
|
+
style={{
|
|
757
|
+
display: "inline-flex",
|
|
758
|
+
gap: 8,
|
|
759
|
+
alignItems: "center",
|
|
760
|
+
fontSize: 13,
|
|
761
|
+
}}
|
|
762
|
+
>
|
|
763
|
+
<span>Z Filter</span>
|
|
764
|
+
<input
|
|
765
|
+
type="text"
|
|
766
|
+
value={zFilterInput}
|
|
767
|
+
onChange={(e) => setZFilterInput(e.target.value)}
|
|
768
|
+
placeholder="1 or 1,2,3"
|
|
769
|
+
style={{
|
|
770
|
+
width: 120,
|
|
771
|
+
padding: "6px 10px",
|
|
772
|
+
borderRadius: 6,
|
|
773
|
+
border: `1px solid ${hasInvalidZFilter ? "#dc2626" : "#cbd5e1"}`,
|
|
774
|
+
background: "white",
|
|
775
|
+
fontSize: 13,
|
|
776
|
+
}}
|
|
777
|
+
title="Show only exact z matches. Example: 1 matches [1], 1,2,3 matches [1,2,3]."
|
|
778
|
+
/>
|
|
779
|
+
</label>
|
|
727
780
|
</>
|
|
728
781
|
)}
|
|
729
782
|
</div>
|
|
@@ -752,6 +805,7 @@ export const SolverDebugger3d: React.FC<SolverDebugger3dProps> = ({
|
|
|
752
805
|
shrinkBoxes={shrinkBoxes}
|
|
753
806
|
boxShrinkAmount={boxShrinkAmount}
|
|
754
807
|
showBorders={showBorders}
|
|
808
|
+
selectedZValues={selectedZValues}
|
|
755
809
|
/>
|
|
756
810
|
)}
|
|
757
811
|
</div>
|
|
@@ -2,7 +2,7 @@ import RBush from "rbush"
|
|
|
2
2
|
import type { RectDiffExpansionSolverInput } from "../solvers/RectDiffExpansionSolver/RectDiffExpansionSolver"
|
|
3
3
|
import type { SimpleRouteJson } from "../types/srj-types"
|
|
4
4
|
import type { XYRect } from "../rectdiff-types"
|
|
5
|
-
import type { RTreeRect } from "
|
|
5
|
+
import type { RTreeRect } from "../types/capacity-mesh-types"
|
|
6
6
|
import { buildZIndexMap } from "../solvers/RectDiffSeedingSolver/layers"
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
|
-
import type { CapacityMeshNode } from "
|
|
2
|
+
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
3
3
|
import type { SegmentWithAdjacentEmptySpace } from "./FindSegmentsWithAdjacentEmptySpaceSolver"
|
|
4
4
|
import type { GraphicsObject } from "graphics-debug"
|
|
5
5
|
import RBush from "rbush"
|
|
@@ -7,7 +7,7 @@ import { EDGE_MAP, EDGES } from "./edge-constants"
|
|
|
7
7
|
import { getBoundsFromCorners } from "./getBoundsFromCorners"
|
|
8
8
|
import type { Bounds } from "@tscircuit/math-utils"
|
|
9
9
|
import { midpoint, segmentToBoxMinDistance } from "@tscircuit/math-utils"
|
|
10
|
-
import type { XYRect } from "
|
|
10
|
+
import type { XYRect } from "../../rectdiff-types"
|
|
11
11
|
|
|
12
12
|
const EPS = 1e-4
|
|
13
13
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
2
|
import Flatbush from "flatbush"
|
|
3
3
|
import type { GraphicsObject, NinePointAnchor } from "graphics-debug"
|
|
4
|
-
import type { CapacityMeshNode } from "
|
|
4
|
+
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
5
5
|
import { projectToUncoveredSegments } from "./projectToUncoveredSegments"
|
|
6
6
|
import { EDGES } from "./edge-constants"
|
|
7
7
|
import { visuallyOffsetLine } from "./visuallyOffsetLine"
|
|
@@ -3,11 +3,11 @@ import {
|
|
|
3
3
|
definePipelineStep,
|
|
4
4
|
type PipelineStep,
|
|
5
5
|
} from "@tscircuit/solver-utils"
|
|
6
|
-
import type { CapacityMeshNode } from "
|
|
6
|
+
import type { CapacityMeshNode } from "../../types/capacity-mesh-types"
|
|
7
7
|
import type { GraphicsObject } from "graphics-debug"
|
|
8
8
|
import { FindSegmentsWithAdjacentEmptySpaceSolver } from "./FindSegmentsWithAdjacentEmptySpaceSolver"
|
|
9
9
|
import { ExpandEdgesToEmptySpaceSolver } from "./ExpandEdgesToEmptySpaceSolver"
|
|
10
|
-
import type { XYRect } from "
|
|
10
|
+
import type { XYRect } from "../../rectdiff-types"
|
|
11
11
|
|
|
12
12
|
type GapFillSolverInput = {
|
|
13
13
|
meshNodes: CapacityMeshNode[]
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
2
2
|
import type { GraphicsObject } from "graphics-debug"
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
CapacityMeshNode,
|
|
5
|
+
RTreeRect,
|
|
6
|
+
} from "../../types/capacity-mesh-types"
|
|
4
7
|
import { expandRectFromSeed } from "../../utils/expandRectFromSeed"
|
|
5
8
|
import { finalizeRects } from "../../utils/finalizeRects"
|
|
6
9
|
import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
|
|
7
10
|
import { rectsToMeshNodes } from "./rectsToMeshNodes"
|
|
8
11
|
import type { XYRect, Candidate3D, Placed3D } from "../../rectdiff-types"
|
|
9
|
-
import type { Obstacle } from "
|
|
12
|
+
import type { Obstacle } from "../../types/srj-types"
|
|
10
13
|
import RBush from "rbush"
|
|
11
14
|
import { rectToTree } from "../../utils/rectToTree"
|
|
12
15
|
import { sameTreeRect } from "../../utils/sameTreeRect"
|
|
@@ -7,11 +7,14 @@ import type {
|
|
|
7
7
|
Obstacle,
|
|
8
8
|
SimpleRouteConnection,
|
|
9
9
|
SimpleRouteJson,
|
|
10
|
-
} from "
|
|
11
|
-
import type { GridFill3DOptions, XYRect } from "
|
|
12
|
-
import type {
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
} from "../../types/srj-types"
|
|
11
|
+
import type { GridFill3DOptions, XYRect } from "../../rectdiff-types"
|
|
12
|
+
import type {
|
|
13
|
+
CapacityMeshNode,
|
|
14
|
+
RTreeRect,
|
|
15
|
+
} from "../../types/capacity-mesh-types"
|
|
16
|
+
import { RectDiffSeedingSolver } from "../RectDiffSeedingSolver/RectDiffSeedingSolver"
|
|
17
|
+
import { RectDiffExpansionSolver } from "../RectDiffExpansionSolver/RectDiffExpansionSolver"
|
|
15
18
|
import type { GraphicsObject } from "graphics-debug"
|
|
16
19
|
import RBush from "rbush"
|
|
17
20
|
import { buildObstacleIndexesByLayer } from "./buildObstacleIndexes"
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import type { SimpleRouteJson } from "
|
|
1
|
+
import type { SimpleRouteJson } from "../../types/srj-types"
|
|
2
2
|
import RBush from "rbush"
|
|
3
|
-
import { computeInverseRects } from "
|
|
3
|
+
import { computeInverseRects } from "../RectDiffSeedingSolver/computeInverseRects"
|
|
4
4
|
import {
|
|
5
5
|
buildZIndexMap,
|
|
6
6
|
obstacleToXYRect,
|
|
7
7
|
obstacleZs,
|
|
8
|
-
} from "
|
|
9
|
-
import type { XYRect } from "
|
|
10
|
-
import type { RTreeRect } from "
|
|
11
|
-
import { padRect } from "
|
|
8
|
+
} from "../RectDiffSeedingSolver/layers"
|
|
9
|
+
import type { XYRect } from "../../rectdiff-types"
|
|
10
|
+
import type { RTreeRect } from "../../types/capacity-mesh-types"
|
|
11
|
+
import { padRect } from "../../utils/padRect"
|
|
12
12
|
|
|
13
13
|
export const buildObstacleIndexesByLayer = (params: {
|
|
14
14
|
srj: SimpleRouteJson
|
|
@@ -16,12 +16,12 @@ import { computeCandidates3D } from "./computeCandidates3D"
|
|
|
16
16
|
import { computeEdgeCandidates3D } from "./computeEdgeCandidates3D"
|
|
17
17
|
import { longestFreeSpanAroundZ } from "./longestFreeSpanAroundZ"
|
|
18
18
|
import { allLayerNode } from "../../utils/buildHardPlacedByLayer"
|
|
19
|
-
import { isFullyOccupiedAtPoint } from "
|
|
19
|
+
import { isFullyOccupiedAtPoint } from "../../utils/isFullyOccupiedAtPoint"
|
|
20
20
|
import { resizeSoftOverlaps } from "../../utils/resizeSoftOverlaps"
|
|
21
|
-
import { getColorForZLayer } from "
|
|
21
|
+
import { getColorForZLayer } from "../../utils/getColorForZLayer"
|
|
22
22
|
import RBush from "rbush"
|
|
23
|
-
import type { RTreeRect } from "
|
|
24
|
-
import { rectToTree } from "
|
|
23
|
+
import type { RTreeRect } from "../../types/capacity-mesh-types"
|
|
24
|
+
import { rectToTree } from "../../utils/rectToTree"
|
|
25
25
|
|
|
26
26
|
export type RectDiffSeedingSolverInput = {
|
|
27
27
|
simpleRouteJson: SimpleRouteJson
|
|
@@ -3,7 +3,7 @@ import { EPS, distancePointToRectEdges } from "../../utils/rectdiff-geometry"
|
|
|
3
3
|
import { isFullyOccupiedAtPoint } from "../../utils/isFullyOccupiedAtPoint"
|
|
4
4
|
import { longestFreeSpanAroundZ } from "./longestFreeSpanAroundZ"
|
|
5
5
|
import type RBush from "rbush"
|
|
6
|
-
import type { RTreeRect } from "
|
|
6
|
+
import type { RTreeRect } from "../../types/capacity-mesh-types"
|
|
7
7
|
const quantize = (value: number, precision = 1e-6) =>
|
|
8
8
|
Math.round(value / precision) * precision
|
|
9
9
|
|
|
@@ -3,7 +3,7 @@ import { EPS, distancePointToRectEdges } from "../../utils/rectdiff-geometry"
|
|
|
3
3
|
import { isFullyOccupiedAtPoint } from "../../utils/isFullyOccupiedAtPoint"
|
|
4
4
|
import { longestFreeSpanAroundZ } from "./longestFreeSpanAroundZ"
|
|
5
5
|
import type RBush from "rbush"
|
|
6
|
-
import type { RTreeRect } from "
|
|
6
|
+
import type { RTreeRect } from "../../types/capacity-mesh-types"
|
|
7
7
|
const quantize = (value: number, precision = 1e-6) =>
|
|
8
8
|
Math.round(value / precision) * precision
|
|
9
9
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RTreeRect } from "
|
|
1
|
+
import type { RTreeRect } from "../../types/capacity-mesh-types"
|
|
2
2
|
import type { XYRect } from "../../rectdiff-types"
|
|
3
3
|
import { clamp, containsPoint } from "../../utils/rectdiff-geometry"
|
|
4
4
|
import type RBush from "rbush"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type RBush from "rbush"
|
|
2
2
|
import type { XYRect } from "../rectdiff-types"
|
|
3
3
|
import { EPS, gt, gte, lt, lte, overlaps } from "./rectdiff-geometry"
|
|
4
|
-
import type { RTreeRect } from "
|
|
4
|
+
import type { RTreeRect } from "../types/capacity-mesh-types"
|
|
5
5
|
import { isSelfRect } from "./isSelfRect"
|
|
6
6
|
import {
|
|
7
7
|
searchStripDown,
|
package/lib/utils/isSelfRect.ts
CHANGED
package/lib/utils/rectToTree.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { XYRect } from "
|
|
2
|
-
import type { RTreeRect } from "
|
|
1
|
+
import type { XYRect } from "../rectdiff-types"
|
|
2
|
+
import type { RTreeRect } from "../types/capacity-mesh-types"
|
|
3
3
|
|
|
4
4
|
export const rectToTree = (
|
|
5
5
|
rect: XYRect,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RTreeRect } from "
|
|
1
|
+
import type { RTreeRect } from "../types/capacity-mesh-types"
|
|
2
2
|
import type { Placed3D } from "../rectdiff-types"
|
|
3
3
|
import { overlaps, subtractRect2D, EPS } from "./rectdiff-geometry"
|
|
4
4
|
import type RBush from "rbush"
|
package/lib/utils/searchStrip.ts
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export function normalizeZValues(zValues: number[]): number[] {
|
|
2
|
+
return [...new Set(zValues)].sort((a, b) => a - b)
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function parseZFilterInput(input: string): number[] | null {
|
|
6
|
+
const zValues: number[] = []
|
|
7
|
+
let current = ""
|
|
8
|
+
|
|
9
|
+
for (const char of input) {
|
|
10
|
+
if (char >= "0" && char <= "9") {
|
|
11
|
+
current += char
|
|
12
|
+
continue
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (char === "," || char === " " || char === "\t" || char === "\n") {
|
|
16
|
+
if (char === ",") {
|
|
17
|
+
if (current === "") return null
|
|
18
|
+
zValues.push(Number(current))
|
|
19
|
+
current = ""
|
|
20
|
+
}
|
|
21
|
+
continue
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (current !== "") zValues.push(Number(current))
|
|
28
|
+
if (zValues.length === 0) return null
|
|
29
|
+
|
|
30
|
+
return normalizeZValues(zValues)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function matchesExactZFilter(
|
|
34
|
+
zValues: number[],
|
|
35
|
+
selectedZValues: number[] | null,
|
|
36
|
+
): boolean {
|
|
37
|
+
if (!selectedZValues) return true
|
|
38
|
+
|
|
39
|
+
const normalized = normalizeZValues(zValues)
|
|
40
|
+
if (normalized.length !== selectedZValues.length) return false
|
|
41
|
+
|
|
42
|
+
return normalized.every((z, index) => z === selectedZValues[index])
|
|
43
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/rectdiff",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.34",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "lib/index.ts",
|
|
6
|
+
"types": "lib/index.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.ts"
|
|
9
|
+
},
|
|
6
10
|
"scripts": {
|
|
7
11
|
"start": "cosmos",
|
|
8
12
|
"build": "tsup-node ./lib/index.ts --format esm --dts",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { matchesExactZFilter, parseZFilterInput } from "../lib/utils/z-filter"
|
|
3
|
+
|
|
4
|
+
test("parseZFilterInput parses a single z", () => {
|
|
5
|
+
expect(parseZFilterInput("1")).toEqual([1])
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
test("parseZFilterInput parses multiple z values", () => {
|
|
9
|
+
expect(parseZFilterInput("1, 2,3")).toEqual([1, 2, 3])
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test("parseZFilterInput normalizes duplicates and ordering", () => {
|
|
13
|
+
expect(parseZFilterInput("3,1,3,2")).toEqual([1, 2, 3])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
test("parseZFilterInput returns null for empty input", () => {
|
|
17
|
+
expect(parseZFilterInput(" ")).toBeNull()
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
test("parseZFilterInput returns null for invalid input", () => {
|
|
21
|
+
expect(parseZFilterInput("1,a")).toBeNull()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("matchesExactZFilter only matches exact single-layer selections", () => {
|
|
25
|
+
expect(matchesExactZFilter([1], [1])).toBe(true)
|
|
26
|
+
expect(matchesExactZFilter([1, 2], [1])).toBe(false)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("matchesExactZFilter only matches exact shared-layer selections", () => {
|
|
30
|
+
expect(matchesExactZFilter([1, 2, 3], [1, 2, 3])).toBe(true)
|
|
31
|
+
expect(matchesExactZFilter([1, 2], [1, 2, 3])).toBe(false)
|
|
32
|
+
expect(matchesExactZFilter([1, 2, 3, 4], [1, 2, 3])).toBe(false)
|
|
33
|
+
})
|