Skip to content

Instantly share code, notes, and snippets.

@rvodden
Last active September 17, 2021 11:51
Show Gist options
  • Save rvodden/2b1467693448a6159d7d625ba9ad905c to your computer and use it in GitHub Desktop.
Save rvodden/2b1467693448a6159d7d625ba9ad905c to your computer and use it in GitHub Desktop.
PySketcher Geometric Solver Recap of Part 2
from abc import ABC, abstractmethod
class Constraint(ABC):
"""Used to restrict that value of a ```ConstrainedValue```."""
@abstractmethod
def validate_object(self, instance):
"""Validates that `instance` is suitable. Raises `InvalidConstraintException` if not"""
raise NotImplementedError("`validate_object` must be implemented explicitly.")
@abstractmethod
def apply_reciprocal_constraint(self, instance):
"""Applies a matching constraint to the provided instance."""
raise NotImplementedError("`apply_reciprocal_callback` must be implemented explicitly.")
@abstractmethod
def cascade_constraints(self, instance):
"""Applies appropriate constraints to the properties of `instance`."""
raise NotImplementedError("`cascade_constraints` must be implemented explicitly.")
class FixedValueConstraint(Constraint):
def __init__(self, value):
self._value = value
@property
def value(self):
return self._value
def __repr__(self):
return f"{self.__class__.__name__}<{self.value}>"
def validate_object(self, instance):
pass
def apply_reciprocal_constraint(self, instance):
pass
def cascade_constraints(self, instance):
pass
class LinkedValueConstraint(Constraint):
def __init__(self, constraint_set):
self._constraint_set = constraint_set
@property
def constraint_set(self):
return self._constraint_set
def __repr__(self):
return f"{self.__class__.__name__}<{self.constraint_set}>"
def validate_object(self, instance):
if not isinstance(instance, ConstraintSet):
raise InvalidConstraintException(f"{self.__class__.__name__} can only"
f" be applied to `ConstraintSet`, it cannot be applied to `{point.__class__.__name__}`")
def apply_reciprocal_constraint(self, instance):
self.constraint_set.constrain_with(LinkedValueConstraint(instance))
def cascade_constraints(self, instance):
pass
def __eq__(self, other):
return isinstance(other, self.__class__) and self.constraint_set == other.constraint_set
class InvalidConstraintException(RuntimeError):
"""Indicates that a constraint has been applied to an object which doesn't make sense."""
class InfluencedConstraint(Constraint):
#... {{% skip %}}
def __init__(self, constraint):
self._constraint = constraint
@property
def constraint(self):
return self._constraint
def __repr__(self):
return f"{self.__class__.__name__}<{self.constraint}>"
#... {{% /skip %}}
def validate_object(self, instance):
if not isinstance(self, ConstraintSet):
raise InvalidConstraintException(f"{self.__class__.__name__} can only"
f" be applied to `ConstraintSet`, it cannot be applied to `{instance.__class__.__name__}`")
def apply_reciprocal_constraint(self, instance):
pass
def cascade_constraints(self, instance):
pass
class ConstraintSet:
def __init__(self, name=""):
self._constraints = []
self._name = name
def constrain_with(self, constraint):
constraint.validate_object(self)
if constraint in self._constraints:
return
"""Add a constraint to this objects list of constraints."""
self._constraints.append(constraint)
constraint.cascade_constraints(self)
constraint.apply_reciprocal_constraint(self)
def reset_constraints(self):
"""Removes the existing constraints from the constraint set"""
self._constraints = []
def resolve(self):
"""Naive implementation to aid testing"""
for constraint in self._constraints:
if isinstance(constraint, FixedValueConstraint):
return constraint.value
if isinstance(constraint, LinkedValueConstraint):
return constraint.constraint_set.resolve()
raise UnderconstrainedError("Fixed Value has not been provided.")
def __repr__(self):
retval = f"{self.__class__.__name__}("
if len(self._constraints) == 0:
retval += ")"
return retval
for constraint in self._constraints:
retval += f"\n {constraint}"
retval += "\n)"
return retval
def __str__(self):
return self._name
class ConstrainedValue:
"""An object which can be passed around to represent a value."""
def __set_name__(self, owner, name):
self.public_name = name
self.private_name = f"_{name}"
def __get__(self, instance, typ=None):
# grab the ConstraintSet from the instance
constraint_set = getattr(instance, self.private_name, None)
# If the instance didn't have an initialized ConstraintSet then
# give it one
if constraint_set is None:
constraint_set = ConstraintSet(f"{instance.name}.{self.public_name}")
setattr(instance, self.private_name, constraint_set)
return constraint_set
def __set__(self, instance, value):
# Grab the ConstraintSet from the instance
constraint_set = self.__get__(instance, None)
constraint_set.reset_constraints()
# if the value we've been asked to assign is a ConstraintSet
# then add a LinkedValueConstraint:
if isinstance(value, ConstraintSet):
constraint_set.constrain_with(LinkedValueConstraint(value))
return
# otherwise use a FixedValueConstraint to constrain to the provided
# value
constraint_set.constrain_with(FixedValueConstraint(value))
class Point(ConstraintSet):
x = ConstrainedValue()
y = ConstrainedValue()
def __init__(self, name="", x=None, y=None):
super().__init__(self)
self._name = name
if x is not None:
self.x = x
if y is not None :
self.y = y
@property
def name(self):
return self._name
#... {{% /skip %}}
class Line:
def __init__(self, x1, y1, x2, y2):
self.x1 = x1
self.y1 = y1
self.x2 = x2
self.y2 = y2
def __repr__(self):
return f"Line<({self.x1},{self.y1}),({self.x2},{self.y2})>"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment