11"""
22The tolerance module provides functionality to deal with tolerances consistently across all other COMPAS packages.
3+
4+ The module provides:
5+ - :class:`Tolerance`: A class for tolerance settings that can be instantiated independently.
6+ - :obj:`TOL`: The global tolerance instance used throughout COMPAS (in-process).
7+
8+ To modify global tolerance settings, use the explicit methods on `TOL`:
9+ - ``TOL.update(...)`` - Update specific tolerance values
10+ - ``TOL.reset()`` - Reset to default values
11+ - ``TOL.temporary(...)`` - Context manager for temporary changes
12+
13+ Example
14+ -------
15+ >>> from compas.tolerance import TOL, Tolerance
16+ >>> # Create an independent tolerance instance
17+ >>> my_tol = Tolerance(absolute=0.01)
18+ >>> my_tol.absolute
19+ 0.01
20+ >>> # Global TOL is unchanged
21+ >>> TOL.absolute
22+ 1e-09
23+ >>> # To modify global state, use update()
24+ >>> TOL.update(absolute=0.001)
25+ >>> TOL.absolute
26+ 0.001
27+ >>> TOL.reset()
28+
329"""
430
531from __future__ import absolute_import
632from __future__ import division
733from __future__ import print_function
834
35+ from contextlib import contextmanager
936from decimal import Decimal
1037
1138import compas
@@ -21,6 +48,21 @@ class Tolerance(Data):
2148 ----------
2249 unit : {"M", "MM"}, optional
2350 The unit of the tolerance settings.
51+ absolute : float, optional
52+ The absolute tolerance. Default is :attr:`ABSOLUTE`.
53+ relative : float, optional
54+ The relative tolerance. Default is :attr:`RELATIVE`.
55+ angular : float, optional
56+ The angular tolerance. Default is :attr:`ANGULAR`.
57+ approximation : float, optional
58+ The tolerance used in approximation processes. Default is :attr:`APPROXIMATION`.
59+ precision : int, optional
60+ The precision used when converting numbers to strings. Default is :attr:`PRECISION`.
61+ lineardeflection : float, optional
62+ The maximum distance between a curve/surface and its polygonal approximation.
63+ Default is :attr:`LINEARDEFLECTION`.
64+ angulardeflection : float, optional
65+ The maximum curvature deviation. Default is :attr:`ANGULARDEFLECTION`.
2466 name : str, optional
2567 The name of the tolerance settings.
2668
@@ -53,25 +95,35 @@ class Tolerance(Data):
5395 This value is called the "true value".
5496 By convention, the second value is considered the "true value" by the comparison functions of this class.
5597
56- The :class:`compas.tolerance.Tolerance` class is implemented using a "singleton" pattern and can therefore have only 1 (one) instance per context.
57- Usage of :attr:`compas.tolerance.TOL` outside of :mod:`compas` internals is therefore deprecated.
98+ Each call to ``Tolerance(...)`` creates an independent instance. To modify the global
99+ tolerance settings used throughout COMPAS, use the explicit methods on :obj:`TOL`:
100+
101+ - ``TOL.update(...)`` - Update specific tolerance values
102+ - ``TOL.reset()`` - Reset all values to defaults
103+ - ``TOL.temporary(...)`` - Context manager for temporary changes
58104
59105 Examples
60106 --------
61- >>> tol = Tolerance()
62- >>> tol.unit
63- 'M'
107+ Create an independent tolerance instance:
108+
109+ >>> tol = Tolerance(absolute=0.01)
64110 >>> tol.absolute
111+ 0.01
112+
113+ The global TOL is separate:
114+
115+ >>> from compas.tolerance import TOL
116+ >>> TOL.absolute # unchanged
65117 1e-09
66- >>> tol.relative
67- 1e-06
68- >>> tol.angular
69- 1e-06
70118
71- """
119+ Modify global state explicitly:
72120
73- _instance = None
74- _is_inited = False
121+ >>> TOL.update(absolute=0.001)
122+ >>> TOL.absolute
123+ 0.001
124+ >>> TOL.reset()
125+
126+ """
75127
76128 SUPPORTED_UNITS = ["M" , "MM" ]
77129 """{"M", "MM"}: Default tolerances are defined in relation to length units.
@@ -120,12 +172,6 @@ class Tolerance(Data):
120172
121173 """
122174
123- def __new__ (cls , * args , ** kwargs ):
124- if not cls ._instance :
125- cls ._instance = object .__new__ (cls , * args , ** kwargs )
126- cls ._is_inited = False
127- return cls ._instance
128-
129175 @property
130176 def __data__ (self ):
131177 return {
@@ -160,22 +206,19 @@ def __init__(
160206 angular = None ,
161207 approximation = None ,
162208 precision = None ,
163- lineardflection = None ,
164- angulardflection = None ,
209+ lineardeflection = None ,
210+ angulardeflection = None ,
165211 name = None ,
166212 ):
167213 super (Tolerance , self ).__init__ (name = name )
168- if not self ._is_inited :
169- self ._unit = None
170- self ._absolute = None
171- self ._relative = None
172- self ._angular = None
173- self ._approximation = None
174- self ._precision = None
175- self ._lineardeflection = None
176- self ._angulardeflection = None
177-
178- self ._is_inited = True
214+ self ._unit = None
215+ self ._absolute = None
216+ self ._relative = None
217+ self ._angular = None
218+ self ._approximation = None
219+ self ._precision = None
220+ self ._lineardeflection = None
221+ self ._angulardeflection = None
179222
180223 if unit is not None :
181224 self .unit = unit
@@ -189,13 +232,10 @@ def __init__(
189232 self .approximation = approximation
190233 if precision is not None :
191234 self .precision = precision
192- if lineardflection is not None :
193- self .lineardeflection = lineardflection
194- if angulardflection is not None :
195- self .angulardeflection = angulardflection
196-
197- # this can be autogenerated if we use slots
198- # __repr__: return f"{__class__.__name__}({', '.join(f'{k}={v!r}' for k, v in self.__dict__.items())})}"
235+ if lineardeflection is not None :
236+ self .lineardeflection = lineardeflection
237+ if angulardeflection is not None :
238+ self .angulardeflection = angulardeflection
199239
200240 def __repr__ (self ):
201241 return "Tolerance(unit='{}', absolute={}, relative={}, angular={}, approximation={}, precision={}, lineardeflection={}, angulardeflection={})" .format (
@@ -220,7 +260,7 @@ def reset(self):
220260 self ._angulardeflection = None
221261
222262 def update_from_dict (self , tolerance ):
223- """Update the tolerance singleton from the key-value pairs found in a dict.
263+ """Update the tolerance from the key-value pairs found in a dict.
224264
225265 Parameters
226266 ----------
@@ -236,6 +276,161 @@ def update_from_dict(self, tolerance):
236276 if hasattr (self , name ):
237277 setattr (self , name , tolerance [name ])
238278
279+ def update (
280+ self ,
281+ unit = None ,
282+ absolute = None ,
283+ relative = None ,
284+ angular = None ,
285+ approximation = None ,
286+ precision = None ,
287+ lineardeflection = None ,
288+ angulardeflection = None ,
289+ ):
290+ """Update tolerance settings.
291+
292+ Only the provided parameters will be updated; others remain unchanged.
293+ Use this method to explicitly modify tolerance settings.
294+
295+ Parameters
296+ ----------
297+ unit : {"M", "MM"}, optional
298+ The unit of the tolerance settings.
299+ absolute : float, optional
300+ The absolute tolerance.
301+ relative : float, optional
302+ The relative tolerance.
303+ angular : float, optional
304+ The angular tolerance.
305+ approximation : float, optional
306+ The tolerance used in approximation processes.
307+ precision : int, optional
308+ The precision used when converting numbers to strings.
309+ lineardeflection : float, optional
310+ The maximum distance between a curve/surface and its polygonal approximation.
311+ angulardeflection : float, optional
312+ The maximum curvature deviation.
313+
314+ Returns
315+ -------
316+ None
317+
318+ Examples
319+ --------
320+ >>> from compas.tolerance import TOL
321+ >>> TOL.update(absolute=0.001, precision=6)
322+ >>> TOL.absolute
323+ 0.001
324+ >>> TOL.precision
325+ 6
326+ >>> TOL.reset()
327+
328+ """
329+ if unit is not None :
330+ self .unit = unit
331+ if absolute is not None :
332+ self .absolute = absolute
333+ if relative is not None :
334+ self .relative = relative
335+ if angular is not None :
336+ self .angular = angular
337+ if approximation is not None :
338+ self .approximation = approximation
339+ if precision is not None :
340+ self .precision = precision
341+ if lineardeflection is not None :
342+ self .lineardeflection = lineardeflection
343+ if angulardeflection is not None :
344+ self .angulardeflection = angulardeflection
345+
346+ @contextmanager
347+ def temporary (
348+ self ,
349+ unit = None ,
350+ absolute = None ,
351+ relative = None ,
352+ angular = None ,
353+ approximation = None ,
354+ precision = None ,
355+ lineardeflection = None ,
356+ angulardeflection = None ,
357+ ):
358+ """Context manager for temporarily changing tolerance settings.
359+
360+ The original settings are automatically restored when the context exits,
361+ even if an exception occurs.
362+
363+ Parameters
364+ ----------
365+ unit : {"M", "MM"}, optional
366+ The unit of the tolerance settings.
367+ absolute : float, optional
368+ The absolute tolerance.
369+ relative : float, optional
370+ The relative tolerance.
371+ angular : float, optional
372+ The angular tolerance.
373+ approximation : float, optional
374+ The tolerance used in approximation processes.
375+ precision : int, optional
376+ The precision used when converting numbers to strings.
377+ lineardeflection : float, optional
378+ The maximum distance between a curve/surface and its polygonal approximation.
379+ angulardeflection : float, optional
380+ The maximum curvature deviation.
381+
382+ Yields
383+ ------
384+ :class:`Tolerance`
385+ The tolerance instance with temporary settings applied.
386+
387+ Examples
388+ --------
389+ >>> from compas.tolerance import TOL
390+ >>> TOL.absolute
391+ 1e-09
392+ >>> with TOL.temporary(absolute=0.01):
393+ ... TOL.absolute
394+ 0.01
395+ >>> TOL.absolute
396+ 1e-09
397+
398+ """
399+ # Save current state
400+ saved = {
401+ "unit" : self ._unit ,
402+ "absolute" : self ._absolute ,
403+ "relative" : self ._relative ,
404+ "angular" : self ._angular ,
405+ "approximation" : self ._approximation ,
406+ "precision" : self ._precision ,
407+ "lineardeflection" : self ._lineardeflection ,
408+ "angulardeflection" : self ._angulardeflection ,
409+ }
410+ try :
411+ # Apply temporary changes
412+ self .update (
413+ unit = unit ,
414+ absolute = absolute ,
415+ relative = relative ,
416+ angular = angular ,
417+ approximation = approximation ,
418+ precision = precision ,
419+ lineardeflection = lineardeflection ,
420+ angulardeflection = angulardeflection ,
421+ )
422+ yield self
423+ finally :
424+ # Restore original state
425+ self ._unit = saved ["unit" ]
426+ self ._absolute = saved ["absolute" ]
427+ self ._relative = saved ["relative" ]
428+ self ._angular = saved ["angular" ]
429+ self ._approximation = saved ["approximation" ]
430+ self ._precision = saved ["precision" ]
431+ self ._lineardeflection = saved ["lineardeflection" ]
432+ self ._angulardeflection = saved ["angulardeflection" ]
433+
239434 @property
240435 def units (self ):
241436 return self ._unit
0 commit comments