diff --git a/CHANGELOG.md b/CHANGELOG.md index afce9e69104..1e7a4ebea29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added `compas.colors.Color.contrast`. * Added `compas.geometry.Brep.from_plane`. * Added `compas.tolerance.Tolerance.angulardeflection`. +* Added `compas.tolerance.Tolerance.update_from_dict`. ### Changed @@ -34,6 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Changed use of `compas.geometry.allclose` to `compas.tolerance.TOL.is_allclose`. * Changed use of `compas.geometry.close` to `compas.tolerance.TOL.is_close`. * Changed imports of itertools to `compas.itertools` instead of `compas.utilities`. +* Changed `compas.tolerance.Tolerance` to a singleton, to ensure having only library-wide tolerance values. ### Removed diff --git a/src/compas/tolerance.py b/src/compas/tolerance.py index c3c8008008b..da6ea00ff5e 100644 --- a/src/compas/tolerance.py +++ b/src/compas/tolerance.py @@ -53,6 +53,9 @@ class Tolerance(Data): This value is called the "true value". By convention, the second value is considered the "true value" by the comparison functions of this class. + The :class:`compas.tolerance.Tolerance` class is implemented using a "singleton" pattern and can therefore have only 1 (one) instance per context. + Usage of :attr:`compas.tolerance.TOL` outside of :mod:`compas` internals is therefore deprecated. + Examples -------- >>> tol = Tolerance() @@ -67,6 +70,9 @@ class Tolerance(Data): """ + _instance = None + _is_inited = False + ABSOLUTE = 1e-9 """float: Determines when a number is small enough to be considered zero. """ @@ -108,8 +114,9 @@ class Tolerance(Data): """ def __new__(cls, *args, **kwargs): - if not hasattr(cls, "_instance"): - cls._instance = super(Tolerance, cls).__new__(cls) + if not isinstance(cls._instance, cls): + cls._instance = object.__new__(cls, *args, **kwargs) + cls._inited = False return cls._instance @property @@ -151,22 +158,34 @@ def __init__( name=None, ): super(Tolerance, self).__init__(name=name) - self._unit = None - self._absolute = None - self._relative = None - self._angular = None - self._approximation = None - self._precision = None - self._lineardeflection = None - self._angulardeflection = None - self.unit = unit - self.absolute = absolute - self.relative = relative - self.angular = angular - self.approximation = approximation - self.precision = precision - self.lineardeflection = lineardflection - self.angulardeflection = angulardflection + if not self._is_inited: + self._unit = None + self._absolute = None + self._relative = None + self._angular = None + self._approximation = None + self._precision = None + self._lineardeflection = None + self._angulardeflection = None + + self._is_inited = True + + if unit is not None: + self.unit = unit + if absolute is not None: + self.absolute = absolute + if relative is not None: + self.relative = relative + if angular is not None: + self.angular = angular + if approximation is not None: + self.approximation = approximation + if precision is not None: + self.precision = precision + if lineardflection is not None: + self.lineardeflection = lineardflection + if angulardflection is not None: + self.angulardeflection = angulardflection # this can be autogenerated if we use slots # __repr__: return f"{__class__.__name__}({', '.join(f'{k}={v!r}' for k, v in self.__dict__.items())})}" @@ -193,6 +212,23 @@ def reset(self): self._lineardeflection = None self._angulardeflection = None + def update_from_dict(self, tolerance): + """Update the tolerance singleton from the key-value pairs found in a dict. + + Parameters + ---------- + tolerance : dict + A dictionary containing named tolerance values. + + Returns + ------- + None + + """ + for name in tolerance: + if hasattr(self, name): + setattr(self, name, tolerance[name]) + @property def units(self): return self._unit