Coverage for trimesh/iteration.py: 100%
24 statements
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
« prev ^ index » next coverage.py v7.14.1, created at 2026-06-24 04:40 +0000
1from math import log2
2from typing import Any
4from .typed import Callable, Iterable, NDArray, Sequence
7def reduce_cascade(operation: Callable, items: Sequence | NDArray):
8 """
9 Call an operation function in a cascaded pairwise way against a
10 flat list of items.
12 This should produce the same result as `functools.reduce`
13 if `operation` is commutable like addition or multiplication.
14 This may be faster for an `operation` that runs with a speed
15 proportional to its largest input, which mesh booleans appear to.
17 The union of a large number of small meshes appears to be
18 "much faster" using this method.
20 This only differs from `functools.reduce` for commutative `operation`
21 in that it returns `None` on empty inputs rather than `functools.reduce`
22 which raises a `TypeError`.
24 For example on `a b c d e f g` this function would run and return:
25 a b
26 c d
27 e f
28 ab cd
29 ef g
30 abcd efg
31 -> abcdefg
33 Where `functools.reduce` would run and return:
34 a b
35 ab c
36 abc d
37 abcd e
38 abcde f
39 abcdef g
40 -> abcdefg
42 Parameters
43 ----------
44 operation
45 The function to call on pairs of items.
46 items
47 The flat list of items to apply operation against.
48 """
49 if len(items) == 0:
50 return None
51 elif len(items) == 1:
52 # skip the loop overhead for a single item
53 return items[0]
54 elif len(items) == 2:
55 # skip the loop overhead for a single pair
56 return operation(items[0], items[1])
58 for _ in range(int(1 + log2(len(items)))):
59 results = []
61 # loop over pairs of items.
62 items_mod = len(items) % 2
63 for i in range(0, len(items) - items_mod, 2):
64 results.append(operation(items[i], items[i + 1]))
66 # if we had a non-even number of items it will have been
67 # skipped by the loop so append it to our list
68 if items_mod != 0:
69 results.append(items[-1])
71 items = results
73 # logic should have reduced to a single item
74 assert len(results) == 1
76 return results[0]
79def chain(*args: Iterable[Any] | Any | None) -> list[Any]:
80 """
81 A less principled version of `list(itertools.chain(*args))` that
82 accepts non-iterable values, filters `None`, and returns a list
83 rather than yielding values.
85 If all passed values are iterables this will return identical
86 results to `list(itertools.chain(*args))`.
89 Examples
90 ----------
92 In [1]: list(itertools.chain([1,2], [3]))
93 Out[1]: [1, 2, 3]
95 In [2]: trimesh.util.chain([1,2], [3])
96 Out[2]: [1, 2, 3]
98 In [3]: trimesh.util.chain([1,2], [3], 4)
99 Out[3]: [1, 2, 3, 4]
101 In [4]: list(itertools.chain([1,2], [3], 4))
102 ----> 1 list(itertools.chain([1,2], [3], 4))
103 TypeError: 'int' object is not iterable
105 In [5]: trimesh.util.chain([1,2], None, 3, None, [4], [], [], 5, [])
106 Out[5]: [1, 2, 3, 4, 5]
109 Parameters
110 -----------
111 args
112 Will be individually checked to see if they're iterable
113 before either being appended or extended to a flat list.
116 Returns
117 ----------
118 chained
119 The values in a flat list.
120 """
121 # collect values to a flat list
122 chained = []
123 # extend if it's a sequence, otherwise append
124 [
125 chained.extend(a)
126 if (hasattr(a, "__iter__") and not isinstance(a, (str, bytes)))
127 else chained.append(a)
128 for a in args
129 if a is not None
130 ]
131 return chained