Skip to content

Commit ce6617b

Browse files
committed
Make Probe work (and test)
Some collateral damage: - change grader names to something supposedly more intuitive - change the 'tank' example to demonstrate simple grading - change AxisType definition to DirectionType to avoid confusion with 'axis' method arguments
1 parent b0335af commit ce6617b

File tree

17 files changed

+215
-119
lines changed

17 files changed

+215
-119
lines changed

src/classy_blocks/construct/operations/operation.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from classy_blocks.construct.flat.face import Face
1212
from classy_blocks.construct.point import Point
1313
from classy_blocks.grading.chop import Chop
14-
from classy_blocks.types import AxisType, ChopArgs, NPPointType, OrientType, PointType, ProjectToType, VectorType
14+
from classy_blocks.types import ChopArgs, DirectionType, NPPointType, OrientType, PointType, ProjectToType, VectorType
1515
from classy_blocks.util import constants
1616
from classy_blocks.util import functions as f
1717
from classy_blocks.util.constants import SIDES_MAP
@@ -34,7 +34,7 @@ def __init__(self, bottom_face: Face, top_face: Face):
3434
self.side_patches: List[Optional[str]] = [None, None, None, None]
3535

3636
# instructions for cell counts and gradings
37-
self.chops: Dict[AxisType, List[Chop]] = {0: [], 1: [], 2: []}
37+
self.chops: Dict[DirectionType, List[Chop]] = {0: [], 1: [], 2: []}
3838

3939
# optionally, put the block in a cell zone
4040
self.cell_zone = ""
@@ -58,7 +58,7 @@ def add_side_edge(self, corner_idx: int, edge_data: EdgeData) -> None:
5858

5959
self.side_edges[corner_idx] = edge_data
6060

61-
def chop(self, axis: AxisType, **kwargs: Unpack[ChopArgs]) -> None:
61+
def chop(self, axis: DirectionType, **kwargs: Unpack[ChopArgs]) -> None:
6262
"""Chop the operation (count/grading) either in given axis:
6363
0: along first edge of a face
6464
1: along second edge of a face
@@ -86,10 +86,10 @@ def chop(self, axis: AxisType, **kwargs: Unpack[ChopArgs]) -> None:
8686

8787
self.chops[axis].append(Chop(**kwargs))
8888

89-
def unchop(self, axis: Optional[AxisType] = None) -> None:
89+
def unchop(self, axis: Optional[DirectionType] = None) -> None:
9090
"""Removes existing chops from an operation (comes handy after copying etc.)"""
9191
if axis is None:
92-
for i in get_args(AxisType):
92+
for i in get_args(DirectionType):
9393
self.chops[i] = []
9494
return
9595

src/classy_blocks/construct/shape.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
from classy_blocks.construct.flat.sketch import Sketch, SketchT
1111
from classy_blocks.construct.operations.loft import Loft
1212
from classy_blocks.construct.operations.operation import Operation
13-
from classy_blocks.types import AxisType, NPPointType, VectorType
13+
from classy_blocks.types import DirectionType, NPPointType, VectorType
1414
from classy_blocks.util import functions as f
1515

1616
ShapeT = TypeVar("ShapeT", bound="Shape")
@@ -106,7 +106,7 @@ def grid(self):
106106
"""Analogous to Sketch's grid but corresponsing operations are returned"""
107107
return self.lofts
108108

109-
def chop(self, axis: AxisType, **kwargs) -> None:
109+
def chop(self, axis: DirectionType, **kwargs) -> None:
110110
"""Chops operations along given axis.
111111
Only axis 0 and 1 are allowed as defined in sketch_1"""
112112
if axis == 2:

src/classy_blocks/construct/shapes/round.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55
from classy_blocks.construct.flat.sketches.annulus import Annulus
66
from classy_blocks.construct.operations.loft import Loft
77
from classy_blocks.construct.shape import LoftedShape
8-
from classy_blocks.types import AxisType, OrientType
8+
from classy_blocks.types import DirectionType, OrientType
99

1010

1111
class RoundSolidShape(LoftedShape):
12-
axial_axis: AxisType = 2 # Axis along which 'outer sides' run
13-
radial_axis: AxisType = 0 # Axis that goes from center to 'outer side'
14-
tangential_axis: AxisType = 1 # Axis that goes around the circumference of the shape
12+
axial_axis: DirectionType = 2 # Axis along which 'outer sides' run
13+
radial_axis: DirectionType = 0 # Axis that goes from center to 'outer side'
14+
tangential_axis: DirectionType = 1 # Axis that goes around the circumference of the shape
1515

1616
start_patch: OrientType = "bottom" # Sides of blocks that define the start patch
1717
end_patch: OrientType = "top" # Sides of blocks that define the end patch"""

src/classy_blocks/construct/stack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from classy_blocks.construct.flat.sketch import Sketch
88
from classy_blocks.construct.operations.operation import Operation
99
from classy_blocks.construct.shape import LoftedShape
10-
from classy_blocks.types import AxisType, PointType, VectorType
10+
from classy_blocks.types import DirectionType, PointType, VectorType
1111
from classy_blocks.util import functions as f
1212

1313

@@ -24,7 +24,7 @@ def grid(self):
2424
first two dimensions within a shape, the third along the stack."""
2525
return [shape.grid for shape in self.shapes]
2626

27-
def get_slice(self, axis: AxisType, index: int) -> List[Operation]:
27+
def get_slice(self, axis: DirectionType, index: int) -> List[Operation]:
2828
"""Returns all operation with given index in specified axis.
2929
For cartesian grids this is equivalent to 'lofts on the same plane';
3030
This does not work with custom/mapped sketches that do not

src/classy_blocks/grading/autograding/grader.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
from classy_blocks.grading.autograding.params import ChopParams, SimpleChopParams, SimpleHighReChopParams
55
from classy_blocks.grading.autograding.probe import Probe
66
from classy_blocks.mesh import Mesh
7-
from classy_blocks.types import AxisType
7+
from classy_blocks.types import DirectionType
88

99

1010
class GraderBase(abc.ABC):
@@ -16,14 +16,16 @@ def __init__(self, mesh: Mesh, params: ChopParams):
1616

1717
@abc.abstractmethod
1818
def grade(self) -> None:
19-
pass
19+
self.mesh.assemble()
2020

2121

2222
class FixedCountGrader(GraderBase):
2323
def __init__(self, mesh: Mesh, params: SimpleChopParams):
2424
super().__init__(mesh, params)
2525

2626
def grade(self):
27+
super().grade()
28+
2729
# just throw the same count into all blocks and be done
2830
chops = self.params.get_chops_from_length(0)
2931

@@ -36,16 +38,18 @@ class SimpleGrader(GraderBase):
3638
def __init__(self, mesh: Mesh, params: SimpleHighReChopParams):
3739
super().__init__(mesh, params)
3840

39-
def grade_axis(self, axis: AxisType) -> None:
40-
for layer in self.probe.get_layers(axis):
41+
def grade_axis(self, axis: DirectionType) -> None:
42+
for layer in self.probe.get_rows(axis):
4143
# TODO: get "take" from the user
4244
length = layer.get_length("max")
43-
print(length)
45+
4446
chops = self.params.get_chops_from_length(length)
4547

4648
for block in layer.blocks:
4749
block.axes[axis].chops = chops
4850

4951
def grade(self):
50-
for axis in get_args(AxisType):
52+
super().grade()
53+
54+
for axis in get_args(DirectionType):
5155
self.grade_axis(axis)
Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,56 @@
11
import functools
2-
from typing import List, Set, get_args
2+
from typing import Dict, List, Set, get_args
33

44
from classy_blocks.items.block import Block
55
from classy_blocks.items.wires.axis import Axis
66
from classy_blocks.mesh import Mesh
7-
from classy_blocks.types import AxisType, ChopTakeType
7+
from classy_blocks.types import ChopTakeType, DirectionType
88

99

1010
@functools.lru_cache(maxsize=3000) # that's for 1000 blocks
11-
def _get_block_from_axis(mesh: Mesh, axis: Axis) -> Block:
11+
def get_block_from_axis(mesh: Mesh, axis: Axis) -> Block:
1212
for block in mesh.blocks:
13-
for index in get_args(AxisType):
14-
if block.axes[index] == axis:
15-
return block
13+
if axis in block.axes:
14+
return block
1615

1716
raise RuntimeError("Block for Axis not found!")
1817

1918

20-
class Layer:
21-
"""A collection of all blocks on a specific AxisIndex"""
19+
class Instruction:
20+
"""A descriptor that tells in which direction the specific block can be chopped."""
2221

23-
def __init__(self, axis: AxisType, blocks: Set[Block]):
24-
self.axis = axis
25-
self.blocks = blocks
22+
def __init__(self, block: Block):
23+
self.block = block
24+
self.directions: List[bool] = [False] * 3
25+
26+
@property
27+
def is_defined(self):
28+
return all(self.directions)
29+
30+
def __hash__(self) -> int:
31+
return id(self)
32+
33+
34+
class Row:
35+
"""A string of blocks that must share the same count
36+
because they sit next to each other.
37+
38+
This needs not be an actual 'string' of blocks because,
39+
depending on blocking, a whole 'layer' of blocks can be
40+
chopped by specifying a single block only (for example, direction 2 in a cylinder)"""
41+
42+
def __init__(self, direction: DirectionType):
43+
self.direction = direction
44+
self.blocks: Set[Block] = set()
45+
46+
def add_block(self, block: Block) -> None:
47+
self.blocks.add(block)
2648

2749
def get_length(self, take: ChopTakeType = "avg"):
2850
lengths: List[float] = []
2951

3052
for block in self.blocks:
31-
lengths += block.axes[self.axis].lengths
53+
lengths += block.axes[self.direction].lengths
3254

3355
if take == "min":
3456
return min(lengths)
@@ -40,74 +62,72 @@ def get_length(self, take: ChopTakeType = "avg"):
4062

4163

4264
class Catalogue:
43-
"""A collection of layers on a specified axis"""
65+
"""A collection of rows on a specified axis"""
4466

45-
def __init__(self, axis: AxisType):
46-
self.axis = axis
47-
self.layers: List[Layer] = []
67+
def __init__(self, mesh: Mesh):
68+
self.mesh = mesh
4869

49-
def has_block(self, block: Block) -> bool:
50-
for layer in self.layers:
51-
if block in layer.blocks:
52-
return True
70+
self.rows: Dict[DirectionType, List[Row]] = {0: [], 1: [], 2: []}
71+
self.instructions = [Instruction(block) for block in mesh.blocks]
5372

54-
return False
73+
for i in get_args(DirectionType):
74+
self._populate(i)
5575

56-
def add_layer(self, blocks: Set[Block]):
57-
"""Adds a block to the appropriate Layer"""
58-
layer = Layer(self.axis, blocks)
59-
self.layers.append(layer)
76+
def _get_undefined_instructions(self, direction: DirectionType) -> List[Instruction]:
77+
return [i for i in self.instructions if not i.directions[direction]]
6078

61-
def get_layer(self, block: Block):
62-
for layer in self.layers:
63-
if block in layer.blocks:
64-
return layer
79+
def _find_instruction(self, block: Block):
80+
# TODO: perform dedumbing on this exquisite piece of code
81+
for instruction in self.instructions:
82+
if instruction.block == block:
83+
return instruction
6584

66-
# TODO: create a custom exception
67-
raise RuntimeError("No such layer!")
85+
raise RuntimeError(f"No instruction found for block {block}")
6886

87+
def _add_block_to_row(self, row: Row, instruction: Instruction, direction: DirectionType) -> None:
88+
row.add_block(instruction.block)
89+
instruction.directions[direction] = True
6990

70-
class Probe:
71-
"""Examines the mesh and gathers required data for auto chopping"""
91+
block = instruction.block
7292

73-
def __init__(self, mesh: Mesh):
74-
self.mesh = mesh
93+
for neighbour_axis in block.axes[direction].neighbours:
94+
neighbour_block = get_block_from_axis(self.mesh, neighbour_axis)
7595

76-
self.catalogues = [Catalogue(axis) for axis in get_args(AxisType)]
96+
if neighbour_block in row.blocks:
97+
continue
7798

78-
for block in self.mesh.blocks:
79-
for axis in get_args(AxisType):
80-
if self.catalogues[axis].has_block(block):
81-
continue
99+
instruction = self._find_instruction(neighbour_block)
82100

83-
self.catalogues[axis].add_layer(self._get_blocks_on_layer(block, axis))
101+
self._add_block_to_row(row, instruction, neighbour_block.get_axis_direction(neighbour_axis))
84102

85-
def _get_block_from_axis(self, axis: Axis) -> Block:
86-
return _get_block_from_axis(self.mesh, axis)
103+
def _populate(self, direction: DirectionType) -> None:
104+
while True:
105+
undefined_instructions = self._get_undefined_instructions(direction)
106+
if len(undefined_instructions) == 0:
107+
break
87108

88-
def _get_blocks_on_layer(self, block: Block, axis: AxisType) -> Set[Block]:
89-
"""Returns all blocks on the same 'layer' as the one in arguments"""
90-
# blocks to be returned
91-
blocks: Set[Block] = set()
92-
# blocks not to check again
93-
traversed: Set[Block] = set()
109+
row = Row(direction)
110+
self._add_block_to_row(row, undefined_instructions[0], direction)
111+
self.rows[direction].append(row)
94112

95-
def check(blk: Block):
96-
if blk not in traversed:
97-
traversed.add(blk)
113+
def get_row_blocks(self, block: Block, direction: DirectionType) -> Set[Block]:
114+
for row in self.rows[direction]:
115+
if block in row.blocks:
116+
return row.blocks
98117

99-
for neighbour_axis in blk.axes[axis].neighbours:
100-
neighbour_block = self._get_block_from_axis(neighbour_axis)
101-
blocks.add(neighbour_block)
118+
# TODO: make a custom exception
119+
raise RuntimeError(f"Direction {direction} of {block} not in catalogue")
102120

103-
check(self._get_block_from_axis(neighbour_axis))
104121

105-
check(block)
122+
class Probe:
123+
"""Examines the mesh and gathers required data for auto chopping"""
106124

107-
return blocks
125+
def __init__(self, mesh: Mesh):
126+
self.mesh = mesh
127+
self.catalogue = Catalogue(self.mesh)
108128

109-
def get_blocks_on_layer(self, block: Block, axis: AxisType):
110-
return self.catalogues[axis].get_layer(block).blocks
129+
def get_row_blocks(self, block: Block, direction: DirectionType) -> Set[Block]:
130+
return self.catalogue.get_row_blocks(block, direction)
111131

112-
def get_layers(self, axis: AxisType) -> List[Layer]:
113-
return self.catalogues[axis].layers
132+
def get_rows(self, direction: DirectionType) -> List[Row]:
133+
return self.catalogue.rows[direction]

src/classy_blocks/items/block.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from classy_blocks.items.vertex import Vertex
66
from classy_blocks.items.wires.axis import Axis
77
from classy_blocks.items.wires.wire import Wire
8-
from classy_blocks.types import AxisType, IndexType
8+
from classy_blocks.types import DirectionType, IndexType
99
from classy_blocks.util import constants
1010
from classy_blocks.util.frame import Frame
1111

@@ -24,12 +24,12 @@ def __init__(self, index: int, vertices: List[Vertex]):
2424
self.wires = Frame[Wire]()
2525

2626
# create wires and connections for quicker addressing
27-
for axis in range(3):
28-
for pair in constants.AXIS_PAIRS[axis]:
29-
wire = Wire(self.vertices, axis, pair[0], pair[1])
27+
for direction in range(3):
28+
for pair in constants.AXIS_PAIRS[direction]:
29+
wire = Wire(self.vertices, direction, pair[0], pair[1])
3030
self.wires.add_beam(pair[0], pair[1], wire)
3131

32-
self.axes = [Axis(i, self.wires.get_axis_beams(i)) for i in get_args(AxisType)]
32+
self.axes = [Axis(i, self.wires.get_axis_beams(i)) for i in get_args(DirectionType)]
3333

3434
# cellZone to which the block belongs to
3535
self.cell_zone: str = ""
@@ -47,9 +47,17 @@ def add_edge(self, corner_1: int, corner_2: int, edge: Edge):
4747

4848
self.wires[corner_1][corner_2].edge = edge
4949

50-
def get_axis_wires(self, axis: AxisType) -> List[Wire]:
50+
def get_axis_wires(self, direction: DirectionType) -> List[Wire]:
5151
"""Returns a list of wires that run in the given axis"""
52-
return self.wires.get_axis_beams(axis)
52+
return self.wires.get_axis_beams(direction)
53+
54+
def get_axis_direction(self, axis: Axis) -> DirectionType:
55+
for i in get_args(DirectionType):
56+
if self.axes[i] == axis:
57+
return i
58+
59+
# TODO: use a custom exception
60+
raise RuntimeError("Axis not in this block!")
5361

5462
def add_neighbour(self, candidate: "Block") -> None:
5563
"""Add a block to neighbours, if applicable"""
@@ -67,8 +75,8 @@ def add_neighbour(self, candidate: "Block") -> None:
6775
for cnd_wire in candidate.wire_list:
6876
this_wire.add_coincident(cnd_wire)
6977

70-
def add_chops(self, axis: AxisType, chops: List[Chop]) -> None:
71-
self.axes[axis].chops += chops
78+
def add_chops(self, direction: DirectionType, chops: List[Chop]) -> None:
79+
self.axes[direction].chops += chops
7280

7381
def update_wires(self) -> None:
7482
for wire in self.wire_list:
@@ -145,3 +153,6 @@ def description(self) -> str:
145153

146154
def __hash__(self) -> int:
147155
return self.index
156+
157+
def __repr__(self) -> str:
158+
return f"Block {self.index}"

0 commit comments

Comments
 (0)