[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/mdecourse/solvespace/python/cython/python_solvespace/slvs.pyx [Back]  [Original]

# -*- coding: utf-8 -*-
# cython: language_level=3, embedsignature=True, cdivision=True

"""Wrapper source code of Solvespace.

author: Yuan Chang
copyright: Copyright (C) 2016-2019
license: GPLv3+
email: pyslvs@gmail.com
"""

from cpython.mem cimport PyMem_Malloc, PyMem_Free
from cpython.object cimport Py_EQ, Py_NE
from libcpp.pair cimport pair
from collections import Counter


cpdef tuple quaternion_u(double qw, double qx, double qy, double qz):
    """Input quaternion, return unit vector of U axis.

    Where `qw`, `qx`, `qy`, `qz` are corresponded to the W, X, Y, Z value of 
    quaternion.
    """
    cdef double x, y, z
    Slvs_QuaternionU(qw, qx, qy, qz, &x, &y, &z)
    return x, y, z


cpdef tuple quaternion_v(double qw, double qx, double qy, double qz):
    """Input quaternion, return unit vector of V axis.

    Signature is same as [quaternion_u](#quaternion_u).
    """
    cdef double x, y, z
    Slvs_QuaternionV(qw, qx, qy, qz, &x, &y, &z)
    return x, y, z


cpdef tuple quaternion_n(double qw, double qx, double qy, double qz):
    """Input quaternion, return unit vector of normal.

    Signature is same as [quaternion_u](#quaternion_u).
    """
    cdef double x, y, z
    Slvs_QuaternionN(qw, qx, qy, qz, &x, &y, &z)
    return x, y, z


cpdef tuple make_quaternion(double ux, double uy, double uz, double vx, double vy, double vz):
    """Input two unit vector, return quaternion.

    Where `ux`, `uy`, `uz` are corresponded to the value of U vector;
    `vx`, `vy`, `vz` are corresponded to the value of V vector.
    """
    cdef double qw, qx, qy, qz
    Slvs_MakeQuaternion(ux, uy, uz, vx, vy, vz, &qw, &qx, &qy, &qz)
    return qw, qx, qy, qz


cdef class Params:

    """Python object to handle multiple parameter handles."""

    @staticmethod
    cdef Params create(Slvs_hParam *p, size_t count):
        """Constructor."""
        cdef Params params = Params.__new__(Params)
        cdef size_t i
        for i in range(count):
            params.param_list.push_back(p[i])
        return params

    def __richcmp__(self, Params other, int op) -> bint:
        """Compare the parameters."""
        if op == Py_EQ:
            return self.param_list == other.param_list
        elif op == Py_NE:
            return self.param_list != other.param_list
        else:
            raise TypeError(
                f"'{op}' not support between instances of "
                f"{type(self)} and {type(other)}"
            )

    def __repr__(self) -> str:
        m = f"{type(self).__name__}(["
        cdef size_t i
        cdef size_t s = self.param_list.size()
        for i in range(s):
            m += str(self.param_list[i])
            if i != s - 1:
                m += ", "
        m += "])"
        return m

# A virtual work plane that present 3D entity or constraint.
cdef Entity _E_FREE_IN_3D = Entity.__new__(Entity)
_E_FREE_IN_3D.t = SLVS_E_WORKPLANE
_E_FREE_IN_3D.h = SLVS_FREE_IN_3D
_E_FREE_IN_3D.g = 0
_E_FREE_IN_3D.params = Params.create(NULL, 0)

# A "None" entity used to fill in constraint option.
cdef Entity _E_NONE = Entity.__new__(Entity)
_E_NONE.t = 0
_E_NONE.h = 0
_E_NONE.g = 0
_E_NONE.params = Params.create(NULL, 0)

# Entity names
_NAME_OF_ENTITIES = {
    SLVS_E_POINT_IN_3D: "point 3d",
    SLVS_E_POINT_IN_2D: "point 2d",
    SLVS_E_NORMAL_IN_2D: "normal 2d",
    SLVS_E_NORMAL_IN_3D: "normal 3d",
    SLVS_E_DISTANCE: "distance",
    SLVS_E_WORKPLANE: "work plane",
    SLVS_E_LINE_SEGMENT: "line segment",
    SLVS_E_CUBIC: "cubic",
    SLVS_E_CIRCLE: "circle",
    SLVS_E_ARC_OF_CIRCLE: "arc",
}

# Constraint names
_NAME_OF_CONSTRAINTS = {
    SLVS_C_POINTS_COINCIDENT: "points coincident",
    SLVS_C_PT_PT_DISTANCE: "point point distance",
    SLVS_C_PT_PLANE_DISTANCE: "point plane distance",
    SLVS_C_PT_LINE_DISTANCE: "point line distance",
    SLVS_C_PT_FACE_DISTANCE: "point face distance",
    SLVS_C_PT_IN_PLANE: "point in plane",
    SLVS_C_PT_ON_LINE: "point on line",
    SLVS_C_PT_ON_FACE: "point on face",
    SLVS_C_EQUAL_LENGTH_LINES: "equal length lines",
    SLVS_C_LENGTH_RATIO: "length ratio",
    SLVS_C_EQ_LEN_PT_LINE_D: "equal length point line distance",
    SLVS_C_EQ_PT_LN_DISTANCES: "equal point line distance",
    SLVS_C_EQUAL_ANGLE: "equal angle",
    SLVS_C_EQUAL_LINE_ARC_LEN: "equal line arc length",
    SLVS_C_SYMMETRIC: "symmetric",
    SLVS_C_SYMMETRIC_HORIZ: "symmetric horizontal",
    SLVS_C_SYMMETRIC_VERT: "symmetric vertical",
    SLVS_C_SYMMETRIC_LINE: "symmetric line",
    SLVS_C_AT_MIDPOINT: "at midpoint",
    SLVS_C_HORIZONTAL: "horizontal",
    SLVS_C_VERTICAL: "vertical",
    SLVS_C_DIAMETER: "diameter",
    SLVS_C_PT_ON_CIRCLE: "point on circle",
    SLVS_C_SAME_ORIENTATION: "same orientation",
    SLVS_C_ANGLE: "angle",
    SLVS_C_PARALLEL: "parallel",
    SLVS_C_PERPENDICULAR: "perpendicular",
    SLVS_C_ARC_LINE_TANGENT: "arc line tangent",
    SLVS_C_CUBIC_LINE_TANGENT: "cubic line tangent",
    SLVS_C_EQUAL_RADIUS: "equal radius",
    SLVS_C_PROJ_PT_DISTANCE: "project point distance",
    SLVS_C_WHERE_DRAGGED: "where dragged",
    SLVS_C_CURVE_CURVE_TANGENT: "curve curve tangent",
    SLVS_C_LENGTH_DIFFERENCE: "length difference",
}


cdef class Entity:

    """The handles of entities."""

    FREE_IN_3D = _E_FREE_IN_3D
    NONE = _E_NONE

    @staticmethod
    cdef Entity create(Slvs_Entity *e, size_t p_size):
        """Constructor."""
        cdef Entity entity = Entity.__new__(Entity)
        with nogil:
            entity.t = e.type
            entity.h = e.h
            entity.wp = e.wrkpl
            entity.g = e.group
        entity.params = Params.create(e.param, p_size)
        return entity

    def __richcmp__(self, Entity other, int op) -> bint:
        """Compare the entities."""
        if op == Py_EQ:
            return (
                self.t == other.t and
                self.h == other.h and
                self.wp == other.wp and
                self.g == other.g and
                self.params == other.params
            )
        elif op == Py_NE:
            return not (self == other)
        else:
            raise TypeError(
                f"'{op}' not support between instances of "
                f"{type(self)} and {type(other)}"
            )

    cpdef bint is_3d(self):
        """Return True if this is a 3D entity."""
        return self.wp == SLVS_FREE_IN_3D

    cpdef bint is_none(self):
        """Return True if this is a empty entity."""
        return self.h == 0

    cpdef bint is_point_2d(self):
        """Return True if this is a 2D point."""
        return self.t == SLVS_E_POINT_IN_2D

    cpdef bint is_point_3d(self):
        """Return True if this is a 3D point."""
        return self.t == SLVS_E_POINT_IN_3D

    cpdef bint is_point(self):
        """Return True if this is a point."""
        return self.is_point_2d() or self.is_point_3d()

    cpdef bint is_normal_2d(self):
        """Return True if this is a 2D normal."""
        return self.t == SLVS_E_NORMAL_IN_2D

    cpdef bint is_normal_3d(self):
        """Return True if this is a 3D normal."""
        return self.t == SLVS_E_NORMAL_IN_3D

    cpdef bint is_normal(self):
        """Return True if this is a normal."""
        return self.is_normal_2d() or self.is_normal_3d()

    cpdef bint is_distance(self):
        """Return True if this is a distance."""
        return self.t == SLVS_E_DISTANCE

    cpdef bint is_work_plane(self):
        """Return True if this is a work plane."""
        return self.t == SLVS_E_WORKPLANE

    cpdef bint is_line_2d(self):
        """Return True if this is a 2D line."""
        return self.is_line() and not self.is_3d()

    cpdef bint is_line_3d(self):
        """Return True if this is a 3D line."""
        return self.is_line() and self.is_3d()

    cpdef bint is_line(self):
        """Return True if this is a line."""
        return self.t == SLVS_E_LINE_SEGMENT

    cpdef bint is_cubic(self):
        """Return True if this is a cubic."""
        return self.t == SLVS_E_CUBIC

    cpdef bint is_circle(self):
        """Return True if this is a circle."""
        return self.t == SLVS_E_CIRCLE

    cpdef bint is_arc(self):
        """Return True if this is a arc."""
        return self.t == SLVS_E_ARC_OF_CIRCLE

    def __repr__(self) -> str:
        cdef int h = self.h
        cdef int g = self.g
        cdef str t = _NAME_OF_ENTITIES[self.t]
        return (
            f"{type(self).__name__}"
            f"(handle={h}, group={g}, type=, is_3d={self.is_3d()}, params={self.params})"
        )


cdef class SolverSystem:

    """A solver system of Python-Solvespace.

    The operation of entities and constraints are using the methods of this
    class.
    """

    def __cinit__(self):
        self.g = 0
        self.sys.params = self.sys.entities = self.sys.constraints = 0

    def __dealloc__(self):
        self.free()

    cdef inline void copy_to_sys(self) nogil:
        """Copy data from stack into system."""
        cdef int i = 0
        cdef pair[Slvs_hParam, Slvs_Param] param
        for param in self.param_list:
            self.sys.param[i] = param.second
            i += 1

        i = 0
        cdef Slvs_Entity entity
        for entity in self.entity_list:
            self.sys.entity[i] = entity
            i += 1

        i = 0
        cdef Slvs_Constraint con
        for con in self.cons_list:
            self.sys.constraint[i] = con
            i += 1

    cdef inline void copy_from_sys(self) nogil:
        """Copy data from system into stack."""
        self.param_list.clear()
        self.entity_list.clear()
        self.cons_list.clear()
        cdef int i
        for i in range(self.sys.params):
            self.param_list[self.sys.param[i].h] = self.sys.param[i]
        for i in range(self.sys.entities):
            self.entity_list.push_back(self.sys.entity[i])
        for i in range(self.sys.constraints):
            self.cons_list.push_back(self.sys.constraint[i])

    cpdef void clear(self):
        """Clear the system."""
        self.g = 0
        self.param_list.clear()
        self.entity_list.clear()
        self.cons_list.clear()
        self.failed_list.clear()
        self.free()

    cdef inline void failed_collecting(self) nogil:
        """Collecting the failed constraints."""
        cdef int i
        for i in range(self.sys.faileds):
            self.failed_list.push_back(self.sys.failed[i])

    cdef inline void free(self):
        PyMem_Free(self.sys.param)
        PyMem_Free(self.sys.entity)
        PyMem_Free(self.sys.constraint)
        PyMem_Free(self.sys.failed)
        self.sys.param = NULL
        self.sys.entity = NULL
        self.sys.constraint = NULL
        self.sys.failed = NULL
        self.sys.params = self.sys.entities = self.sys.constraints = 0

    cpdef void set_group(self, size_t g):
        """Set the current group (`g`)."""
        self.g = g

    cpdef int group(self):
        """Return the current group."""
        return self.g

    cpdef void set_params(self, Params p, object params):
        """Set the parameters from a [Params] handle (`p`) belong to this 
        system.
        The values is come from `params`, length must be equal to the handle.
        """
        params = tuple(params)
        cdef int i = p.param_list.size()
        if i != len(params):
            raise ValueError(f"number of parameters {len(params)} are not match {i}")

        i = 0
        cdef Slvs_hParam h
        for h in p.param_list:
            self.param_list[h].val = params[i]
            i += 1

    cpdef tuple params(self, Params p):
        """Get the parameters from a [Params] handle (`p`) belong to this 
        system.
        The length of tuple is decided by handle.
        """
        param_list = []
        cdef Slvs_hParam h
        for h in p.param_list:
            param_list.append(self.param_list[h].val)
        return tuple(param_list)

    cpdef int dof(self):
        """Return the degrees of freedom of current group.
        Only can be called after solving.
        """
        return self.sys.dof

    cpdef object constraints(self):
        """Return the number of each constraint type.
        The name of constraints is represented by string.
        """
        cons_list = []
        cdef Slvs_Constraint con
        for con in self.cons_list:
            cons_list.append(_NAME_OF_CONSTRAINTS[con.type])
        return Counter(cons_list)

    cpdef list failures(self):
        """Return a list of failed constraint numbers."""
        failed_list = []
        cdef Slvs_hConstraint error
        for error in self.failed_list:
            failed_list.append(error)
        return failed_list

    cpdef int solve(self):
        """Start the solving, return the result flag."""
        # Parameters
        self.sys.param = PyMem_Malloc(self.param_list.size() * sizeof(Slvs_Param))
        # Entities
        self.sys.entity = PyMem_Malloc(self.entity_list.size() * sizeof(Slvs_Entity))
        # Constraints
        cdef size_t cons_size = self.cons_list.size()
        self.sys.constraint = PyMem_Malloc(cons_size * sizeof(Slvs_Constraint))
        self.sys.failed = PyMem_Malloc(cons_size * sizeof(Slvs_hConstraint))
        self.sys.faileds = cons_size

        # Copy to system
        self.copy_to_sys()
        # Solve
        Slvs_Solve(&self.sys, self.g)
        # Failed constraints and free memory.
        self.copy_from_sys()
        self.failed_collecting()
        self.free()
        return self.sys.result

    cpdef Entity create_2d_base(self):
        """Create a 2D system on current group,
        return the handle of work plane.
        """
        cdef double qw, qx, qy, qz
        qw, qx, qy, qz = make_quaternion(1, 0, 0, 0, 1, 0)
        cdef Entity nm = self.add_normal_3d(qw, qx, qy, qz)
        return self.add_work_plane(self.add_point_3d(0, 0, 0), nm)

    cdef inline Slvs_hParam new_param(self, double val) nogil:
        """Add a parameter."""
        self.sys.params += 1
        cdef Slvs_hParam h = self.sys.params
        self.param_list[h] = Slvs_MakeParam(h, self.g, val)
        return h

    cdef inline Slvs_hEntity eh(self) nogil:
        """Return new entity handle."""
        self.sys.entities += 1
        return self.sys.entities

    cpdef Entity add_point_2d(self, double u, double v, Entity wp):
        """Add a 2D point to specific work plane (`wp`) then return the handle.

        Where `u`, `v` are corresponded to the value of U, V axis on the work
        plane.
        """
        if wp is None or not wp.is_work_plane():
            raise TypeError(f"{wp} is not a work plane")

        cdef Slvs_hParam u_p = self.new_param(u)
        cdef Slvs_hParam v_p = self.new_param(v)
        cdef Slvs_Entity e = Slvs_MakePoint2d(self.eh(), self.g, wp.h, u_p, v_p)
        self.entity_list.push_back(e)

        return Entity.create(&e, 2)

    cpdef Entity add_point_3d(self, double x, double y, double z):
        """Add a 3D point then return the handle.

        Where `x`, `y`, `z` are corresponded to the value of X, Y, Z axis.
        """
        cdef Slvs_hParam x_p = self.new_param(x)
        cdef Slvs_hParam y_p = self.new_param(y)
        cdef Slvs_hParam z_p = self.new_param(z)
        cdef Slvs_Entity e = Slvs_MakePoint3d(self.eh(), self.g, x_p, y_p, z_p)
        self.entity_list.push_back(e)

        return Entity.create(&e, 3)

    cpdef Entity add_normal_2d(self, Entity wp):
        """Add a 2D normal orthogonal to specific work plane (`wp`)
        then return the handle.
        """
        if wp is None or not wp.is_work_plane():
            raise TypeError(f"{wp} is not a work plane")
        cdef Slvs_Entity e = Slvs_MakeNormal2d(self.eh(), self.g, wp.h)
        self.entity_list.push_back(e)
        return Entity.create(&e, 0)

    cpdef Entity add_normal_3d(self, double qw, double qx, double qy, double qz):
        """Add a 3D normal from quaternion then return the handle.

        Where `qw`, `qx`, `qy`, `qz` are corresponded to
        the W, X, Y, Z value of quaternion.
        """
        cdef Slvs_hParam w_p = self.new_param(qw)
        cdef Slvs_hParam x_p = self.new_param(qx)
        cdef Slvs_hParam y_p = self.new_param(qy)
        cdef Slvs_hParam z_p = self.new_param(qz)
        self.entity_list.push_back(Slvs_MakeNormal3d(
            self.eh(), self.g, w_p, x_p, y_p, z_p))
        return Entity.create(&self.entity_list.back(), 4)

    cpdef Entity add_distance(self, double d, Entity wp):
        """Add a distance to specific work plane (`wp`) then return the handle.

        Where `d` is distance value.
        """
        if wp is None or not wp.is_work_plane():
            raise TypeError(f"{wp} is not a work plane")

        cdef Slvs_hParam d_p = self.new_param(d)
        self.entity_list.push_back(Slvs_MakeDistance(
            self.eh(), self.g, wp.h, d_p))
        return Entity.create(&self.entity_list.back(), 1)

    cpdef Entity add_line_2d(self, Entity p1, Entity p2, Entity wp):
        """Add a 2D line to specific work plane (`wp`) then return the handle.

        Where `p1` is the start point; `p2` is the end point.
        """
        if wp is None or not wp.is_work_plane():
            raise TypeError(f"{wp} is not a work plane")
        if p1 is None or not p1.is_point_2d():
            raise TypeError(f"{p1} is not a 2d point")
        if p2 is None or not p2.is_point_2d():
            raise TypeError(f"{p2} is not a 2d point")

        self.entity_list.push_back(Slvs_MakeLineSegment(
            self.eh(), self.g, wp.h, p1.h, p2.h))
        return Entity.create(&self.entity_list.back(), 0)

    cpdef Entity add_line_3d(self, Entity p1, Entity p2):
        """Add a 3D line then return the handle.

        Where `p1` is the start point; `p2` is the end point.
        """
        if p1 is None or not p1.is_point_3d():
            raise TypeError(f"{p1} is not a 3d point")
        if p2 is None or not p2.is_point_3d():
            raise TypeError(f"{p2} is not a 3d point")

        self.entity_list.push_back(Slvs_MakeLineSegment(
            self.eh(), self.g, SLVS_FREE_IN_3D, p1.h, p2.h))
        return Entity.create(&self.entity_list.back(), 0)

    cpdef Entity add_cubic(self, Entity p1, Entity p2, Entity p3, Entity p4, Entity wp):
        """Add a cubic curve to specific work plane (`wp`) then return the
        handle.

        Where `p1` to `p4` is the control points.
        """
        if wp is None or not wp.is_work_plane():
            raise TypeError(f"{wp} is not a work plane")
        if p1 is None or not p1.is_point_2d():
            raise TypeError(f"{p1} is not a 2d point")
        if p2 is None or not p2.is_point_2d():
            raise TypeError(f"{p2} is not a 2d point")
        if p3 is None or not p3.is_point_2d():
            raise TypeError(f"{p3} is not a 2d point")
        if p4 is None or not p4.is_point_2d():
            raise TypeError(f"{p4} is not a 2d point")

        self.entity_list.push_back(Slvs_MakeCubic(
            self.eh(), self.g, wp.h, p1.h, p2.h, p3.h, p4.h))
        return Entity.create(&self.entity_list.back(), 0)

    cpdef Entity add_arc(self, Entity nm, Entity ct, Entity start, Entity end, Entity wp):
        """Add an arc to specific work plane (`wp`) then return the handle.

        Where `nm` is the orthogonal normal; `ct` is the center point;
        `start` is the start point; `end` is the end point.
        """
        if wp is None or not wp.is_work_plane():
            raise TypeError(f"{wp} is not a work plane")
        if nm is None or not nm.is_normal_3d():
            raise TypeError(f"{nm} is not a 3d normal")
        if ct is None or not ct.is_point_2d():
            raise TypeError(f"{ct} is not a 2d point")
        if start is None or not start.is_point_2d():
            raise TypeError(f"{start} is not a 2d point")
        if end is None or not end.is_point_2d():
            raise TypeError(f"{end} is not a 2d point")
        self.entity_list.push_back(Slvs_MakeArcOfCircle(
            self.eh(), self.g, wp.h, nm.h, ct.h, start.h, end.h))
        return Entity.create(&self.entity_list.back(), 0)

    cpdef Entity add_circle(self, Entity nm, Entity ct, Entity radius, Entity wp):
        """Add an circle to specific work plane (`wp`) then return the handle.

        Where `nm` is the orthogonal normal;
        `ct` is the center point;
        `radius` is the distance value represent radius.
        """
        if wp is None or not wp.is_work_plane():
            raise TypeError(f"{wp} is not a work plane")
        if nm is None or not nm.is_normal_3d():
            raise TypeError(f"{nm} is not a 3d normal")
        if ct is None or not ct.is_point_2d():
            raise TypeError(f"{ct} is not a 2d point")
        if radius is None or not radius.is_distance():
            raise TypeError(f"{radius} is not a distance")

        self.entity_list.push_back(Slvs_MakeCircle(self.eh(), self.g, wp.h,
                                                   ct.h, nm.h, radius.h))
        return Entity.create(&self.entity_list.back(), 0)

    cpdef Entity add_work_plane(self, Entity origin, Entity nm):
        """Add a work plane then return the handle.

        Where `origin` is the origin point of the plane;
        `nm` is the orthogonal normal.
        """
        if origin is None or origin.t != SLVS_E_POINT_IN_3D:
            raise TypeError(f"{origin} is not a 3d point")
        if nm is None or nm.t != SLVS_E_NORMAL_IN_3D:
            raise TypeError(f"{nm} is not a 3d normal")

        self.entity_list.push_back(Slvs_MakeWorkplane(self.eh(), self.g, origin.h, nm.h))
        return Entity.create(&self.entity_list.back(), 0)

    cpdef void add_constraint(
        self,
        int c_type,
        Entity wp,
        double v,
        Entity p1,
        Entity p2,
        Entity e1,
        Entity e2,
        Entity e3 = _E_NONE,
        Entity e4 = _E_NONE,
        int other = 0,
        int other2 = 0
    ):
        """Add a constraint by type code `c_type`.
        This is an origin function mapping to different constraint methods.

        Where `wp` represents work plane; `v` represents constraint value;
        `p1` and `p2` represent point entities; `e1` to `e4` represent other 
        types of entity;
        `other` and `other2` are control options of the constraint.
        """
        if wp is None or not wp.is_work_plane():
            raise TypeError(f"{wp} is not a work plane")

        cdef Entity e
        for e in (p1, p2):
            if e is None or not (e.is_none() or e.is_point()):
                raise TypeError(f"{e} is not a point")
        for e in (e1, e2, e3, e4):
            if e is None:
                raise TypeError(f"{e} is not a entity")

        self.sys.constraints += 1
        cdef Slvs_Constraint c
        c.h = self.sys.constraints
        c.group = self.g
        c.type = c_type
        c.wrkpl = wp.h
        c.valA = v
        c.ptA = p1.h
        c.ptB = p2.h
        c.entityA = e1.h
        c.entityB = e2.h
        c.entityC = e3.h
        c.entityD = e4.h
        c.other = other
        c.other2 = other2
        self.cons_list.push_back(c)

    #####
    # Constraint methods.
    #####

    cpdef void coincident(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D):
        """Coincident two entities.

        | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) |
        |:---------------:|:---------------:|:-----------------:|
        | [is_point] | [is_point] | Optional |
        | [is_point] | [is_work_plane] | [Entity.FREE_IN_3D] |
        | [is_point] | [is_line] | Optional |
        | [is_point] | [is_circle] | Optional |
        """
        cdef int t
        if e1.is_point() and e2.is_point():
            self.add_constraint(SLVS_C_POINTS_COINCIDENT, wp, 0., e1, e2,
                                _E_NONE, _E_NONE)
        elif e1.is_point() and e2.is_work_plane() and wp is _E_FREE_IN_3D:
            self.add_constraint(SLVS_C_PT_IN_PLANE, e2, 0., e1, _E_NONE, e2,
                                _E_NONE)
        elif e1.is_point() and (e2.is_line() or e2.is_circle()):
            if e2.is_line():
                t = SLVS_C_PT_ON_LINE
            else:
                t = SLVS_C_PT_ON_CIRCLE
            self.add_constraint(t, wp, 0., e1, _E_NONE, e2, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void distance(
        self,
        Entity e1,
        Entity e2,
        double value,
        Entity wp = _E_FREE_IN_3D
    ):
        """Distance constraint between two entities.

        If `value` is equal to zero, then turn into
        [coincident](#solversystemcoincident)

        | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) |
        |:---------------:|:---------------:|:-----------------:|
        | [is_point] | [is_point] | Optional |
        | [is_point] | [is_work_plane] | [Entity.FREE_IN_3D] |
        | [is_point] | [is_line] | Optional |
        """
        if value == 0.:
            self.coincident(e1, e2, wp)
            return
        if e1.is_point() and e2.is_point():
            self.add_constraint(SLVS_C_PT_PT_DISTANCE, wp, value, e1, e2,
                                _E_NONE, _E_NONE)
        elif e1.is_point() and e2.is_work_plane() and wp is _E_FREE_IN_3D:
            self.add_constraint(SLVS_C_PT_PLANE_DISTANCE, e2, value, e1,
                                _E_NONE, e2, _E_NONE)
        elif e1.is_point() and e2.is_line():
            self.add_constraint(SLVS_C_PT_LINE_DISTANCE, wp, value, e1,
                                _E_NONE, e2, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void equal(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D):
        """Equal constraint between two entities.

       | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) |
       |:---------------:|:---------------:|:-----------------:|
       | [is_line] | [is_line] | Optional |
       | [is_line] | [is_arc] | Optional |
       | [is_line] | [is_circle] | Optional |
       | [is_arc] | [is_arc] | Optional |
       | [is_arc] | [is_circle] | Optional |
       | [is_circle] | [is_circle] | Optional |
       | [is_circle] | [is_arc] | Optional |
       """
        if e1.is_line() and e2.is_line():
            self.add_constraint(SLVS_C_EQUAL_LENGTH_LINES, wp, 0., _E_NONE,
                                _E_NONE, e1, e2)
        elif e1.is_line() and (e2.is_arc() or e2.is_circle()):
            self.add_constraint(SLVS_C_EQUAL_LINE_ARC_LEN, wp, 0., _E_NONE,
                                _E_NONE, e1, e2)
        elif (e1.is_arc() or e1.is_circle()) and (e2.is_arc() or e2.is_circle()):
            self.add_constraint(SLVS_C_EQUAL_RADIUS, wp, 0., _E_NONE, _E_NONE, e1, e2)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void equal_included_angle(
        self,
        Entity e1,
        Entity e2,
        Entity e3,
        Entity e4,
        Entity wp
    ):
        """Constraint that 2D line 1 (`e1`) and line 2 (`e2`),
        line 3 (`e3`) and line 4 (`e4`) must have same included angle on work 
        plane `wp`.
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_line_2d() and e2.is_line_2d() and e3.is_line_2d() and e4.is_line_2d():
            self.add_constraint(SLVS_C_EQUAL_ANGLE, wp, 0., _E_NONE, _E_NONE,
                                e1, e2, e3, e4)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {e3}, {e4}, {wp}")

    cpdef void equal_point_to_line(
        self,
        Entity e1,
        Entity e2,
        Entity e3,
        Entity e4,
        Entity wp
    ):
        """Constraint that point 1 (`e1`) and line 1 (`e2`),
        point 2 (`e3`) and line 2  (`e4`) must have same distance on work 
        plane `wp`.
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_point_2d() and e2.is_line_2d() and e3.is_point_2d() and e4.is_line_2d():
            self.add_constraint(SLVS_C_EQ_PT_LN_DISTANCES, wp, 0., e1, e3, e2, e4)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {e3}, {e4}, {wp}")

    cpdef void ratio(self, Entity e1, Entity e2, double value, Entity wp):
        """The ratio (`value`) constraint between two 2D lines (`e1` and 
        `e2`).
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_line_2d() and e2.is_line_2d():
            self.add_constraint(SLVS_C_EQ_PT_LN_DISTANCES, wp, value, _E_NONE,
                                _E_NONE, e1, e2)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void symmetric(
        self,
        Entity e1,
        Entity e2,
        Entity e3 = _E_NONE,
        Entity wp = _E_FREE_IN_3D
    ):
        """Symmetric constraint between two points.

        | Entity 1 (`e1`) | Entity 2 (`e2`) | Entity 3 (`e3`) | Work plane (`wp`) |
        |:---------------:|:---------------:|:---------------:|:-----------------:|
        | [is_point_3d] | [is_point_3d] | [is_work_plane] | [Entity.FREE_IN_3D] |
        | [is_point_2d] | [is_point_2d] | [is_work_plane] | [Entity.FREE_IN_3D] |
        | [is_point_2d] | [is_point_2d] | [is_line_2d] | Is not [Entity.FREE_IN_3D] |
        """
        if e1.is_point_3d() and e2.is_point_3d() and e3.is_work_plane() and wp is _E_FREE_IN_3D:
            self.add_constraint(SLVS_C_SYMMETRIC, wp, 0., e1, e2, e3, _E_NONE)
        elif e1.is_point_2d() and e2.is_point_2d() and e3.is_work_plane() and wp is _E_FREE_IN_3D:
            self.add_constraint(SLVS_C_SYMMETRIC, e3, 0., e1, e2, e3, _E_NONE)
        elif e1.is_point_2d() and e2.is_point_2d() and e3.is_line_2d():
            if wp is _E_FREE_IN_3D:
                raise ValueError("this is a 2d constraint")
            self.add_constraint(SLVS_C_SYMMETRIC_LINE, wp, 0., e1, e2, e3, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {e3}, {wp}")

    cpdef void symmetric_h(self, Entity e1, Entity e2, Entity wp):
        """Symmetric constraint between two 2D points (`e1` and `e2`)
        with horizontal line on the work plane (`wp` can not be
        [Entity.FREE_IN_3D]).
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_point_2d() and e2.is_point_2d():
            self.add_constraint(SLVS_C_SYMMETRIC_HORIZ, wp, 0., e1, e2, _E_NONE, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void symmetric_v(self, Entity e1, Entity e2, Entity wp):
        """Symmetric constraint between two 2D points (`e1` and `e2`)
        with vertical line on the work plane (`wp` can not be
        [Entity.FREE_IN_3D]).
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_point_2d() and e2.is_point_2d():
            self.add_constraint(SLVS_C_SYMMETRIC_VERT, wp, 0., e1, e2, _E_NONE, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void midpoint(
        self,
        Entity e1,
        Entity e2,
        Entity wp = _E_FREE_IN_3D
    ):
        """Midpoint constraint between a point (`e1`) and
        a line (`e2`) on work plane (`wp`).
        """
        if e1.is_point() and e2.is_line():
            self.add_constraint(SLVS_C_AT_MIDPOINT, wp, 0., e1, _E_NONE, e2, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void horizontal(self, Entity e1, Entity wp):
        """Vertical constraint of a 2d point (`e1`) on
        work plane (`wp` can not be [Entity.FREE_IN_3D]).
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_line_2d():
            self.add_constraint(SLVS_C_HORIZONTAL, wp, 0., _E_NONE, _E_NONE, e1, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {wp}")

    cpdef void vertical(self, Entity e1, Entity wp):
        """Vertical constraint of a 2d point (`e1`) on
        work plane (`wp` can not be [Entity.FREE_IN_3D]).
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_line_2d():
            self.add_constraint(SLVS_C_VERTICAL, wp, 0., _E_NONE, _E_NONE, e1, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {wp}")

    cpdef void diameter(self, Entity e1, double value, Entity wp):
        """Diameter (`value`) constraint of a circular entities.

        | Entity 1 (`e1`) | Work plane (`wp`) |
        |:---------------:|:-----------------:|
        | [is_arc] | Optional |
        | [is_circle] | Optional |
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_arc() or e1.is_circle():
            self.add_constraint(SLVS_C_DIAMETER, wp, value, _E_NONE, _E_NONE,
                                e1, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {wp}")

    cpdef void same_orientation(self, Entity e1, Entity e2):
        """Equal orientation constraint between two 3d normals (`e1` and
        `e2`).
        """
        if e1.is_normal_3d() and e2.is_normal_3d():
            self.add_constraint(SLVS_C_SAME_ORIENTATION, _E_FREE_IN_3D, 0.,
                                _E_NONE, _E_NONE, e1, e2)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}")

    cpdef void angle(self, Entity e1, Entity e2, double value, Entity wp, bint inverse = False):
        """Degrees angle (`value`) constraint between two 2d lines (`e1` and
        `e2`) on the work plane (`wp` can not be [Entity.FREE_IN_3D]).
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_line_2d() and e2.is_line_2d():
            self.add_constraint(SLVS_C_ANGLE, wp, value, _E_NONE, _E_NONE,
                                e1, e2, _E_NONE, _E_NONE, inverse)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void perpendicular(self, Entity e1, Entity e2, Entity wp, bint inverse = False):
        """Perpendicular constraint between two 2d lines (`e1` and `e2`)
        on the work plane (`wp` can not be [Entity.FREE_IN_3D]) with
        `inverse` option.
        """
        if wp is _E_FREE_IN_3D:
            raise ValueError("this is a 2d constraint")
        if e1.is_line_2d() and e2.is_line_2d():
            self.add_constraint(SLVS_C_PERPENDICULAR, wp, 0., _E_NONE, _E_NONE,
                                e1, e2, _E_NONE, _E_NONE, inverse)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void parallel(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D):
        """Parallel constraint between two lines (`e1` and `e2`) on
        the work plane (`wp`).
        """
        if e1.is_line() and e2.is_line():
            self.add_constraint(SLVS_C_PARALLEL, wp, 0., _E_NONE, _E_NONE, e1, e2)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void tangent(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D):
        """Parallel constraint between two entities (`e1` and `e2`) on the
        work plane (`wp`).

        | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) |
        |:---------------:|:---------------:|:-----------------:|
        | [is_arc] | [is_line_2d] | Is not [Entity.FREE_IN_3D] |
        | [is_cubic] | [is_line_3d] | [Entity.FREE_IN_3D] |
        | [is_arc] | [is_cubic] | Is not [Entity.FREE_IN_3D] |
        | [is_arc] | [is_arc] | Is not [Entity.FREE_IN_3D] |
        | [is_cubic] | [is_cubic] | Optional |
        """
        if e1.is_arc() and e2.is_line_2d():
            if wp is _E_FREE_IN_3D:
                raise ValueError("this is a 2d constraint")
            self.add_constraint(SLVS_C_ARC_LINE_TANGENT, wp, 0., _E_NONE, _E_NONE, e1, e2)
        elif e1.is_cubic() and e2.is_line_3d() and wp is _E_FREE_IN_3D:
            self.add_constraint(SLVS_C_CUBIC_LINE_TANGENT, wp, 0., _E_NONE, _E_NONE, e1, e2)
        elif (e1.is_arc() or e1.is_cubic()) and (e2.is_arc() or e2.is_cubic()):
            if (e1.is_arc() or e2.is_arc()) and wp is _E_FREE_IN_3D:
                raise ValueError("this is a 2d constraint")
            self.add_constraint(SLVS_C_CURVE_CURVE_TANGENT, wp, 0., _E_NONE, _E_NONE, e1, e2)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}")

    cpdef void distance_proj(self, Entity e1, Entity e2, double value):
        """Projected distance (`value`) constraint between
        two 3d points (`e1` and `e2`).
        """
        if e1.is_point_3d() and e2.is_point_3d():
            self.add_constraint(SLVS_C_CURVE_CURVE_TANGENT, _E_FREE_IN_3D,
                                value, e1, e2, _E_NONE, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {e2}")

    cpdef void dragged(self, Entity e1, Entity wp = _E_FREE_IN_3D):
        """Dragged constraint of a point (`e1`) on the work plane (`wp`)."""
        if e1.is_point():
            self.add_constraint(SLVS_C_WHERE_DRAGGED, wp, 0., e1, _E_NONE, _E_NONE, _E_NONE)
        else:
            raise TypeError(f"unsupported entities: {e1}, {wp}")

Web Proxy Viewer  |  New URL  |  Original Page