Add cython modules.

pull/493/head
KmolYuan 2019-05-28 20:00:15 +08:00
parent 6352405206
commit 14e28827ee
10 changed files with 1934 additions and 0 deletions

40
cython/README.md Normal file
View File

@ -0,0 +1,40 @@
[![Build status](https://ci.appveyor.com/api/projects/status/b2o8jw7xnfqghqr5?svg=true)](https://ci.appveyor.com/project/KmolYuan/solvespace)
[![Build status](https://travis-ci.org/KmolYuan/solvespace.svg)](https://travis-ci.org/KmolYuan/solvespace)
![OS](https://img.shields.io/badge/OS-Windows%2C%20Mac%20OS%2C%20Ubuntu-blue.svg)
[![GitHub license](https://img.shields.io/badge/license-GPLv3+-blue.svg)](https://raw.githubusercontent.com/KmolYuan/solvespace/master/LICENSE)
python-solvespace
===
Python library from solver of SolveSpace.
Feature for CDemo and Python interface can see [here](https://github.com/KmolYuan/python-solvespace/blob/master/Cython/DOC.txt).
Build and Test
===
Requirement:
+ [Cython]
Build and install the module:
```bash
python setup.py install
python setup.py install --user # User mode
```
Run unit test:
```bash
python tests/test_slvs.py
```
Uninstall the module:
```bash
pip uninstall python_solvespace
```
[GNU Make]: https://sourceforge.net/projects/mingw-w64/files/latest/download?source=files
[Cython]: https://cython.org/

View File

@ -0,0 +1,26 @@
--- cygwinccompiler.py
+++ cygwinccompiler.py
@@ -82,7 +82,25 @@ def get_msvcr():
elif msc_ver == '1600':
# VS2010 / MSVC 10.0
return ['msvcr100']
+ elif msc_ver == '1700':
+ # Visual Studio 2012 / Visual C++ 11.0
+ return ['msvcr110']
+ elif msc_ver == '1800':
+ # Visual Studio 2013 / Visual C++ 12.0
+ return ['msvcr120']
+ elif msc_ver == '1900':
+ # Visual Studio 2015 / Visual C++ 14.0
+ # "msvcr140.dll no longer exists" http://blogs.msdn.com/b/vcblog/archive/2014/06/03/visual-studio-14-ctp.aspx
+ return ['vcruntime140']
+ elif msc_ver == '1910':
+ return ['vcruntime140']
+ elif msc_ver == '1914':
+ return ['vcruntime140']
+ elif msc_ver == '1915':
+ return ['vcruntime140']
+ elif msc_ver == '1916':
+ return ['vcruntime140']
else:
raise ValueError("Unknown MS Compiler version %s " % msc_ver)

View File

@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
"""'python_solvespace' module is a wrapper of
Python binding Solvespace solver libraries.
"""
__author__ = "Yuan Chang"
__copyright__ = "Copyright (C) 2016-2019"
__license__ = "GPLv3+"
__email__ = "pyslvs@gmail.com"
from .slvs import (
quaternion_u,
quaternion_v,
quaternion_n,
make_quaternion,
Constraint,
ResultFlag,
Params,
Entity,
SolverSystem,
)
__all__ = [
'quaternion_u',
'quaternion_v',
'quaternion_n',
'make_quaternion',
'Constraint',
'ResultFlag',
'Params',
'Entity',
'SolverSystem',
]

View File

@ -0,0 +1,325 @@
# -*- coding: utf-8 -*-
# cython: language_level=3
"""Wrapper header of Solvespace.
author: Yuan Chang
copyright: Copyright (C) 2016-2019
license: GPLv3+
email: pyslvs@gmail.com
"""
from libc.stdint cimport uint32_t
from libcpp.vector cimport vector
from libcpp.map cimport map as cmap
cdef extern from "slvs.h" nogil:
ctypedef uint32_t Slvs_hParam
ctypedef uint32_t Slvs_hEntity
ctypedef uint32_t Slvs_hConstraint
ctypedef uint32_t Slvs_hGroup
# Virtual work plane entity
Slvs_hEntity SLVS_FREE_IN_3D
ctypedef struct Slvs_Param:
Slvs_hParam h
Slvs_hGroup group
double val
# Entity type
int SLVS_E_POINT_IN_3D
int SLVS_E_POINT_IN_2D
int SLVS_E_NORMAL_IN_2D
int SLVS_E_NORMAL_IN_3D
int SLVS_E_DISTANCE
int SLVS_E_WORKPLANE
int SLVS_E_LINE_SEGMENT
int SLVS_E_CUBIC
int SLVS_E_CIRCLE
int SLVS_E_ARC_OF_CIRCLE
ctypedef struct Slvs_Entity:
Slvs_hEntity h
Slvs_hGroup group
int type
Slvs_hEntity wrkpl
Slvs_hEntity point[4]
Slvs_hEntity normal
Slvs_hEntity distance
Slvs_hParam param[4]
ctypedef struct Slvs_Constraint:
Slvs_hConstraint h
Slvs_hGroup group
int type
Slvs_hEntity wrkpl
double valA
Slvs_hEntity ptA
Slvs_hEntity ptB
Slvs_hEntity entityA
Slvs_hEntity entityB
Slvs_hEntity entityC
Slvs_hEntity entityD
int other
int other2
ctypedef struct Slvs_System:
Slvs_Param *param
int params
Slvs_Entity *entity
int entities
Slvs_Constraint *constraint
int constraints
Slvs_hParam dragged[4]
int calculateFaileds
Slvs_hConstraint *failed
int faileds
int dof
int result
void Slvs_Solve(Slvs_System *sys, Slvs_hGroup hg)
void Slvs_QuaternionU(
double qw, double qx, double qy, double qz,
double *x, double *y, double *z
)
void Slvs_QuaternionV(
double qw, double qx, double qy, double qz,
double *x, double *y, double *z
)
void Slvs_QuaternionN(
double qw, double qx, double qy, double qz,
double *x, double *y, double *z
)
void Slvs_MakeQuaternion(
double ux, double uy, double uz,
double vx, double vy, double vz,
double *qw, double *qx, double *qy, double *qz
)
Slvs_Param Slvs_MakeParam(Slvs_hParam h, Slvs_hGroup group, double val)
Slvs_Entity Slvs_MakePoint2d(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hParam u, Slvs_hParam v
)
Slvs_Entity Slvs_MakePoint3d(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hParam x, Slvs_hParam y, Slvs_hParam z
)
Slvs_Entity Slvs_MakeNormal3d(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hParam qw, Slvs_hParam qx,
Slvs_hParam qy, Slvs_hParam qz
)
Slvs_Entity Slvs_MakeNormal2d(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl
)
Slvs_Entity Slvs_MakeDistance(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl, Slvs_hParam d
)
Slvs_Entity Slvs_MakeLineSegment(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hEntity ptA, Slvs_hEntity ptB
)
Slvs_Entity Slvs_MakeCubic(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hEntity pt0, Slvs_hEntity pt1,
Slvs_hEntity pt2, Slvs_hEntity pt3
)
Slvs_Entity Slvs_MakeArcOfCircle(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hEntity normal,
Slvs_hEntity center,
Slvs_hEntity start, Slvs_hEntity end
)
Slvs_Entity Slvs_MakeCircle(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity wrkpl,
Slvs_hEntity center,
Slvs_hEntity normal, Slvs_hEntity radius
)
Slvs_Entity Slvs_MakeWorkplane(
Slvs_hEntity h, Slvs_hGroup group,
Slvs_hEntity origin, Slvs_hEntity normal
)
Slvs_Constraint Slvs_MakeConstraint(
Slvs_hConstraint h,
Slvs_hGroup group,
int type,
Slvs_hEntity wrkpl,
double valA,
Slvs_hEntity ptA,
Slvs_hEntity ptB,
Slvs_hEntity entityA,
Slvs_hEntity entityB
)
cpdef enum Constraint:
# Expose macro of constraint types
POINTS_COINCIDENT = 100000
PT_PT_DISTANCE
PT_PLANE_DISTANCE
PT_LINE_DISTANCE
PT_FACE_DISTANCE
PT_IN_PLANE
PT_ON_LINE
PT_ON_FACE
EQUAL_LENGTH_LINES
LENGTH_RATIO
EQ_LEN_PT_LINE_D
EQ_PT_LN_DISTANCES
EQUAL_ANGLE
EQUAL_LINE_ARC_LEN
SYMMETRIC
SYMMETRIC_HORIZ
SYMMETRIC_VERT
SYMMETRIC_LINE
AT_MIDPOINT
HORIZONTAL
VERTICAL
DIAMETER
PT_ON_CIRCLE
SAME_ORIENTATION
ANGLE
PARALLEL
PERPENDICULAR
ARC_LINE_TANGENT
CUBIC_LINE_TANGENT
EQUAL_RADIUS
PROJ_PT_DISTANCE
WHERE_DRAGGED
CURVE_CURVE_TANGENT
LENGTH_DIFFERENCE
cpdef enum ResultFlag:
# Expose macro of result flags
OKAY
INCONSISTENT
DIDNT_CONVERGE
TOO_MANY_UNKNOWNS
cpdef tuple quaternion_u(double qw, double qx, double qy, double qz)
cpdef tuple quaternion_v(double qw, double qx, double qy, double qz)
cpdef tuple quaternion_n(double qw, double qx, double qy, double qz)
cpdef tuple make_quaternion(double ux, double uy, double uz, double vx, double vy, double vz)
cdef class Params:
cdef vector[Slvs_hParam] param_list
@staticmethod
cdef Params create(Slvs_hParam *p, size_t count)
cdef class Entity:
cdef int t
cdef Slvs_hEntity h, wp
cdef Slvs_hGroup g
cdef readonly Params params
@staticmethod
cdef Entity create(Slvs_Entity *e, size_t p_size)
cpdef bint is_3d(self)
cpdef bint is_none(self)
cpdef bint is_point_2d(self)
cpdef bint is_point_3d(self)
cpdef bint is_point(self)
cpdef bint is_normal_2d(self)
cpdef bint is_normal_3d(self)
cpdef bint is_normal(self)
cpdef bint is_distance(self)
cpdef bint is_work_plane(self)
cpdef bint is_line_2d(self)
cpdef bint is_line_3d(self)
cpdef bint is_line(self)
cpdef bint is_cubic(self)
cpdef bint is_circle(self)
cpdef bint is_arc(self)
cdef class SolverSystem:
cdef Slvs_hGroup g
cdef Slvs_System sys
cdef cmap[Slvs_hParam, Slvs_Param] param_list
cdef vector[Slvs_Entity] entity_list
cdef vector[Slvs_Constraint] cons_list
cdef vector[Slvs_hConstraint] failed_list
cdef void copy_to_sys(self) nogil
cdef void copy_from_sys(self) nogil
cpdef void clear(self)
cdef void failed_collecting(self) nogil
cdef void free(self)
cpdef void set_group(self, size_t g)
cpdef int group(self)
cpdef tuple params(self, Params p)
cpdef int dof(self)
cpdef object constraints(self)
cpdef list faileds(self)
cpdef int solve(self)
cpdef Entity create_2d_base(self)
cdef Slvs_hParam new_param(self, double val) nogil
cdef Slvs_hEntity eh(self) nogil
cpdef Entity add_point_2d(self, double u, double v, Entity wp)
cpdef Entity add_point_3d(self, double x, double y, double z)
cpdef Entity add_normal_2d(self, Entity wp)
cpdef Entity add_normal_3d(self, double qw, double qx, double qy, double qz)
cpdef Entity add_distance(self, double d, Entity wp)
cpdef Entity add_line_2d(self, Entity p1, Entity p2, Entity wp)
cpdef Entity add_line_3d(self, Entity p1, Entity p2)
cpdef Entity add_cubic(self, Entity p1, Entity p2, Entity p3, Entity p4, Entity wp)
cpdef Entity add_arc(self, Entity nm, Entity ct, Entity start, Entity end, Entity wp)
cpdef Entity add_circle(self, Entity nm, Entity ct, Entity radius, Entity wp)
cpdef Entity add_work_plane(self, Entity origin, Entity nm)
cpdef void add_constraint(
self,
Constraint c_type,
Entity wp,
double v,
Entity p1,
Entity p2,
Entity e1,
Entity e2,
Entity e3 = *,
Entity e4 = *,
int other = *,
int other2 = *
)
cpdef void coincident(self, Entity e1, Entity e2, Entity wp = *)
cpdef void distance(self, Entity e1, Entity e2, double value, Entity wp = *)
cpdef void equal(self, Entity e1, Entity e2, Entity wp = *)
cpdef void equal_included_angle(self, Entity e1, Entity e2, Entity e3, Entity e4, Entity wp)
cpdef void equal_point_to_line(self, Entity e1, Entity e2, Entity e3, Entity e4, Entity wp)
cpdef void ratio(self, Entity e1, Entity e2, double value, Entity wp)
cpdef void symmetric(self, Entity e1, Entity e2, Entity e3 = *, Entity wp = *)
cpdef void symmetric_h(self, Entity e1, Entity e2, Entity wp)
cpdef void symmetric_v(self, Entity e1, Entity e2, Entity wp)
cpdef void midpoint(self, Entity e1, Entity e2, Entity wp = *)
cpdef void horizontal(self, Entity e1, Entity wp)
cpdef void vertical(self, Entity e1, Entity wp)
cpdef void diameter(self, Entity e1, double value, Entity wp)
cpdef void same_orientation(self, Entity e1, Entity e2)
cpdef void angle(self, Entity e1, Entity e2, double value, Entity wp, bint inverse = *)
cpdef void perpendicular(self, Entity e1, Entity e2, Entity wp, bint inverse = *)
cpdef void parallel(self, Entity e1, Entity e2, Entity wp = *)
cpdef void tangent(self, Entity e1, Entity e2, Entity wp = *)
cpdef void distance_proj(self, Entity e1, Entity e2, double value)
cpdef void dragged(self, Entity e1, Entity wp = *)

View File

@ -0,0 +1,347 @@
# -*- coding: utf-8 -*-
from typing import Tuple, List, Counter
from enum import IntEnum, auto
def quaternion_u(
qw: float,
qx: float,
qy: float,
qz: float
) -> Tuple[float, float, float]:
...
def quaternion_v(
qw: float,
qx: float,
qy: float,
qz: float
) -> Tuple[float, float, float]:
...
def quaternion_n(
qw: float,
qx: float,
qy: float,
qz: float
) -> Tuple[float, float, float]:
...
def make_quaternion(
ux: float,
uy: float,
uz: float,
vx: float,
vy: float,
vz: float
) -> Tuple[float, float, float, float]:
...
class Constraint(IntEnum):
# Expose macro of constraint types
POINTS_COINCIDENT = 100000
PT_PT_DISTANCE = auto()
PT_PLANE_DISTANCE = auto()
PT_LINE_DISTANCE = auto()
PT_FACE_DISTANCE = auto()
PT_IN_PLANE = auto()
PT_ON_LINE = auto()
PT_ON_FACE = auto()
EQUAL_LENGTH_LINES = auto()
LENGTH_RATIO = auto()
EQ_LEN_PT_LINE_D = auto()
EQ_PT_LN_DISTANCES = auto()
EQUAL_ANGLE = auto()
EQUAL_LINE_ARC_LEN = auto()
SYMMETRIC = auto()
SYMMETRIC_HORIZ = auto()
SYMMETRIC_VERT = auto()
SYMMETRIC_LINE = auto()
AT_MIDPOINT = auto()
HORIZONTAL = auto()
VERTICAL = auto()
DIAMETER = auto()
PT_ON_CIRCLE = auto()
SAME_ORIENTATION = auto()
ANGLE = auto()
PARALLEL = auto()
PERPENDICULAR = auto()
ARC_LINE_TANGENT = auto()
CUBIC_LINE_TANGENT = auto()
EQUAL_RADIUS = auto()
PROJ_PT_DISTANCE = auto()
WHERE_DRAGGED = auto()
CURVE_CURVE_TANGENT = auto()
LENGTH_DIFFERENCE = auto()
class ResultFlag(IntEnum):
# Expose macro of result flags
OKAY = 0
INCONSISTENT = auto()
DIDNT_CONVERGE = auto()
TOO_MANY_UNKNOWNS = auto()
class Params:
def __repr__(self) -> str:
...
class Entity:
FREE_IN_3D: Entity
NONE: Entity
params: Params
def is_3d(self) -> bool:
...
def is_none(self) -> bool:
...
def is_point_2d(self) -> bool:
...
def is_point_3d(self) -> bool:
...
def is_point(self) -> bool:
...
def is_normal_2d(self) -> bool:
...
def is_normal_3d(self) -> bool:
...
def is_normal(self) -> bool:
...
def is_distance(self) -> bool:
...
def is_work_plane(self) -> bool:
...
def is_line_2d(self) -> bool:
...
def is_line_3d(self) -> bool:
...
def is_line(self) -> bool:
...
def is_cubic(self) -> bool:
...
def is_circle(self) -> bool:
...
def is_arc(self) -> bool:
...
def __repr__(self) -> str:
...
class SolverSystem:
def __init__(self):
...
def clear(self) -> None:
...
def set_group(self, g: int) -> None:
...
def group(self) -> int:
...
def params(self, p: Params) -> Tuple[float, ...]:
...
def dof(self) -> int:
...
def constraints(self) -> Counter[str]:
...
def faileds(self) -> List[int]:
...
def solve(self) -> ResultFlag:
...
def create_2d_base(self) -> Entity:
...
def add_point_2d(self, u: float, v: float, wp: Entity) -> Entity:
...
def add_point_3d(self, x: float, y: float, z: float) -> Entity:
...
def add_normal_2d(self, wp: Entity) -> Entity:
...
def add_normal_3d(self, qw: float, qx: float, qy: float, qz: float) -> Entity:
...
def add_distance(self, d: float, wp: Entity) -> Entity:
...
def add_line_2d(self, p1: Entity, p2: Entity, wp: Entity) -> Entity:
...
def add_line_3d(self, p1: Entity, p2: Entity) -> Entity:
...
def add_cubic(self, p1: Entity, p2: Entity, p3: Entity, p4: Entity, wp: Entity) -> Entity:
...
def add_arc(self, nm: Entity, ct: Entity, start: Entity, end: Entity, wp: Entity) -> Entity:
...
def add_circle(self, nm: Entity, ct: Entity, radius: Entity, wp: Entity) -> Entity:
...
def add_work_plane(self, origin: Entity, nm: Entity) -> Entity:
...
def add_constraint(
self,
c_type: Constraint,
wp: Entity,
v: float,
p1: Entity,
p2: Entity,
e1: Entity,
e2: Entity,
e3: Entity = Entity.NONE,
e4: Entity = Entity.NONE,
other: int = 0,
other2: int = 0
) -> None:
...
def coincident(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None:
"""Coincident two entities."""
...
def distance(
self,
e1: Entity,
e2: Entity,
value: float,
wp: Entity = Entity.FREE_IN_3D
) -> None:
"""Distance constraint between two entities."""
...
def equal(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None:
"""Equal constraint between two entities."""
...
def equal_included_angle(
self,
e1: Entity,
e2: Entity,
e3: Entity,
e4: Entity,
wp: Entity
) -> None:
"""Constraint that point 1 and line 1, point 2 and line 2
must have same distance.
"""
...
def equal_point_to_line(
self,
e1: Entity,
e2: Entity,
e3: Entity,
e4: Entity,
wp: Entity
) -> None:
"""Constraint that line 1 and line 2, line 3 and line 4
must have same included angle.
"""
...
def ratio(self, e1: Entity, e2: Entity, value: float, wp: Entity) -> None:
"""The ratio constraint between two lines."""
...
def symmetric(
self,
e1: Entity,
e2: Entity,
e3: Entity = Entity.NONE,
wp: Entity = Entity.FREE_IN_3D
) -> None:
"""Symmetric constraint between two points."""
...
def symmetric_h(self, e1: Entity, e2: Entity, wp: Entity) -> None:
"""Symmetric constraint between two points with horizontal line."""
...
def symmetric_v(self, e1: Entity, e2: Entity, wp: Entity) -> None:
"""Symmetric constraint between two points with vertical line."""
...
def midpoint(
self,
e1: Entity,
e2: Entity,
wp: Entity = Entity.FREE_IN_3D
) -> None:
"""Midpoint constraint between a point and a line."""
...
def horizontal(self, e1: Entity, wp: Entity) -> None:
"""Horizontal constraint of a 2d point."""
...
def vertical(self, e1: Entity, wp: Entity) -> None:
"""Vertical constraint of a 2d point."""
...
def diameter(self, e1: Entity, value: float, wp: Entity) -> None:
"""Diameter constraint of a circular entities."""
...
def same_orientation(self, e1: Entity, e2: Entity) -> None:
"""Equal orientation constraint between two 3d normals."""
...
def angle(self, e1: Entity, e2: Entity, value: float, wp: Entity, inverse: bool = False) -> None:
"""Degrees angle constraint between two 2d lines."""
...
def perpendicular(self, e1: Entity, e2: Entity, wp: Entity, inverse: bool = False) -> None:
"""Perpendicular constraint between two 2d lines."""
...
def parallel(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None:
"""Parallel constraint between two lines."""
...
def tangent(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None:
"""Parallel constraint between two entities."""
...
def distance_proj(self, e1: Entity, e2: Entity, value: float) -> None:
"""Projected distance constraint between two 3d points."""
...
def dragged(self, e1: Entity, wp: Entity = Entity.FREE_IN_3D) -> None:
"""Dragged constraint of a point."""
...

View File

@ -0,0 +1,796 @@
# -*- 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
"""
cimport cython
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):
cdef double x, y, z
Slvs_QuaternionV(qw, qx, qy, qz, &x, &y, &z)
return x, y, z
cpdef tuple quaternion_v(double qw, double qx, double qy, double qz):
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):
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):
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 __repr__(self) -> str:
cdef str m = f"{self.__class__.__name__}(["
cdef size_t i
cdef size_t s = self.param_list.size()
for i in range(s):
m += str(<int>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
cdef dict _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
cdef dict _NAME_OF_CONSTRAINTS = {
POINTS_COINCIDENT: "points coincident",
PT_PT_DISTANCE: "point point distance",
PT_PLANE_DISTANCE: "point plane distance",
PT_LINE_DISTANCE: "point line distance",
PT_FACE_DISTANCE: "point face distance",
PT_IN_PLANE: "point in plane",
PT_ON_LINE: "point on line",
PT_ON_FACE: "point on face",
EQUAL_LENGTH_LINES: "equal length lines",
LENGTH_RATIO: "length ratio",
EQ_LEN_PT_LINE_D: "equal length point line distance",
EQ_PT_LN_DISTANCES: "equal point line distance",
EQUAL_ANGLE: "equal angle",
EQUAL_LINE_ARC_LEN: "equal line arc length",
SYMMETRIC: "symmetric",
SYMMETRIC_HORIZ: "symmetric horizontal",
SYMMETRIC_VERT: "symmetric vertical",
SYMMETRIC_LINE: "symmetric line",
AT_MIDPOINT: "at midpoint",
HORIZONTAL: "horizontal",
VERTICAL: "vertical",
DIAMETER: "diameter",
PT_ON_CIRCLE: "point on circle",
SAME_ORIENTATION: "same orientation",
ANGLE: "angle",
PARALLEL: "parallel",
PERPENDICULAR: "perpendicular",
ARC_LINE_TANGENT: "arc line tangent",
CUBIC_LINE_TANGENT: "cubic line tangent",
EQUAL_RADIUS: "equal radius",
PROJ_PT_DISTANCE: "project point distance",
WHERE_DRAGGED: "where dragged",
CURVE_CURVE_TANGENT: "curve curve tangent",
LENGTH_DIFFERENCE: "length difference",
}
cdef class Entity:
"""Python object to handle a pointer of 'Slvs_hEntity'."""
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, other: Entity, op: cython.int) -> 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 (
self.t != other.t or
self.h != other.h or
self.wp != other.wp or
self.g != other.g or
self.params != other.params
)
else:
raise TypeError(
f"'{op}' not support between instances of "
f"{type(self)} and {type(other)}"
)
cpdef bint is_3d(self):
return self.wp == SLVS_FREE_IN_3D
cpdef bint is_none(self):
return self.h == 0
cpdef bint is_point_2d(self):
return self.t == SLVS_E_POINT_IN_2D
cpdef bint is_point_3d(self):
return self.t == SLVS_E_POINT_IN_3D
cpdef bint is_point(self):
return self.is_point_2d() or self.is_point_3d()
cpdef bint is_normal_2d(self):
return self.t == SLVS_E_NORMAL_IN_2D
cpdef bint is_normal_3d(self):
return self.t == SLVS_E_NORMAL_IN_3D
cpdef bint is_normal(self):
return self.is_normal_2d() or self.is_normal_3d()
cpdef bint is_distance(self):
return self.t == SLVS_E_DISTANCE
cpdef bint is_work_plane(self):
return self.t == SLVS_E_WORKPLANE
cpdef bint is_line_2d(self):
return self.is_line() and not self.is_3d()
cpdef bint is_line_3d(self):
return self.is_line() and self.is_3d()
cpdef bint is_line(self):
return self.t == SLVS_E_LINE_SEGMENT
cpdef bint is_cubic(self):
return self.t == SLVS_E_CUBIC
cpdef bint is_circle(self):
return self.t == SLVS_E_CIRCLE
cpdef bint is_arc(self):
return self.t == SLVS_E_ARC_OF_CIRCLE
def __repr__(self) -> str:
cdef int h = <int>self.h
cdef int g = <int>self.g
cdef str t = _NAME_OF_ENTITIES[<int>self.t]
return (
f"{self.__class__.__name__}"
f"(handle={h}, group={g}, type=<{t}>, is_3d={self.is_3d()}, params={self.params})"
)
cdef class SolverSystem:
"""Python object of 'Slvs_System'."""
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):
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 by integer."""
self.g = <Slvs_hGroup>g
cpdef int group(self):
"""Return the current group by integer."""
return <int>self.g
cpdef tuple params(self, Params p):
"""Get the parameters by Params object."""
cdef list 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 DOF of system."""
return self.sys.dof
cpdef object constraints(self):
"""Return the list of all constraints."""
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 faileds(self):
"""Return the count of failed constraint."""
failed_list = []
cdef Slvs_hConstraint error
for error in self.failed_list:
failed_list.append(<int>error)
return failed_list
cpdef int solve(self):
"""Solve the system."""
# Parameters
self.sys.param = <Slvs_Param *>PyMem_Malloc(self.param_list.size() * sizeof(Slvs_Param))
# Entities
self.sys.entity = <Slvs_Entity *>PyMem_Malloc(self.entity_list.size() * sizeof(Slvs_Entity))
# Constraints
cdef size_t cons_size = self.cons_list.size()
self.sys.constraint = <Slvs_Constraint *>PyMem_Malloc(cons_size * sizeof(Slvs_Constraint))
self.sys.failed = <Slvs_hConstraint *>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 basic 2D system and return the 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 = <Slvs_hParam>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 <Slvs_hEntity>self.sys.entities
cpdef Entity add_point_2d(self, double u, double v, Entity wp):
"""Add 2D point."""
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 3D point."""
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."""
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."""
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 2D distance."""
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."""
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."""
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 2D cubic."""
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 2D arc."""
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 a 2D circle."""
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 3D work plane."""
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,
Constraint 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 customized 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 = <Slvs_hConstraint>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."""
cdef Constraint t
if e1.is_point() and e2.is_point():
self.add_constraint(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(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 = PT_ON_LINE
else:
t = 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 == 0.:
self.coincident(e1, e2, wp)
return
if e1.is_point() and e2.is_point():
self.add_constraint(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(PT_PLANE_DISTANCE, e2, value, e1, _E_NONE, e2, _E_NONE)
elif e1.is_point() and e2.is_line():
self.add_constraint(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."""
if e1.is_line() and e2.is_line():
self.add_constraint(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(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(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 line 1 and line 2, line 3 and line 4
must have same included angle.
"""
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(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 and line 1, point 2 and line 2
must have same distance.
"""
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(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 constraint between two lines."""
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(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."""
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(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(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(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 points with horizontal line."""
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(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 points with vertical line."""
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(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 and a line."""
if e1.is_point() and e2.is_line():
self.add_constraint(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):
"""Horizontal constraint of a 2d point."""
if wp is _E_FREE_IN_3D:
raise ValueError("this is a 2d constraint")
if e1.is_line_2d():
self.add_constraint(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."""
if wp is _E_FREE_IN_3D:
raise ValueError("this is a 2d constraint")
if e1.is_line_2d():
self.add_constraint(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 constraint of a circular entities."""
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(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."""
if e1.is_normal_3d() and e2.is_normal_3d():
self.add_constraint(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 constraint between two 2d lines."""
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(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."""
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(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."""
if e1.is_line() and e2.is_line():
self.add_constraint(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."""
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(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(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(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 constraint between two 3d points."""
if e1.is_point_3d() and e2.is_point_3d():
self.add_constraint(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."""
if e1.is_point():
self.add_constraint(WHERE_DRAGGED, wp, 0., e1, _E_NONE, _E_NONE, _E_NONE)
else:
raise TypeError(f"unsupported entities: {e1}, {wp}")

1
cython/requirements.txt Normal file
View File

@ -0,0 +1 @@
cython

116
cython/setup.py Normal file
View File

@ -0,0 +1,116 @@
# -*- coding: utf-8 -*-
"""Compile the Cython libraries of Python-Solvespace."""
__author__ = "Yuan Chang"
__copyright__ = "Copyright (C) 2016-2019"
__license__ = "GPLv3+"
__email__ = "pyslvs@gmail.com"
import os
import re
import codecs
from setuptools import setup, Extension, find_packages
from platform import system
from distutils import sysconfig
here = os.path.abspath(os.path.dirname(__file__))
include_path = '../include/'
src_path = '../src/'
platform_path = src_path + 'platform/'
ver = sysconfig.get_config_var('VERSION')
lib = sysconfig.get_config_var('BINDIR')
def read(*parts):
with codecs.open(os.path.join(here, *parts), 'r') as f:
return f.read()
def get_version(*file_paths):
doc = read(*file_paths)
m1 = re.search(r"^set\(solvespace_VERSION_MAJOR (\d)\)", doc, re.M)
m2 = re.search(r"^set\(solvespace_VERSION_MINOR (\d)\)", doc, re.M)
if m1 and m2:
return f"{m1.group(1)}.{m2.group(1)}"
raise RuntimeError("Unable to find version string.")
macros = [
('_hypot', 'hypot'),
('M_PI', 'PI'), # C++ 11
('ISOLATION_AWARE_ENABLED', None),
('LIBRARY', None),
('EXPORT_DLL', None),
('_CRT_SECURE_NO_WARNINGS', None),
]
compile_args = [
'-O3',
'-Wno-cpp',
'-g',
'-Wno-write-strings',
'-fpermissive',
'-fPIC',
'-std=c++11',
]
sources = [
'python_solvespace/' + 'slvs.pyx',
src_path + 'util.cpp',
src_path + 'entity.cpp',
src_path + 'expr.cpp',
src_path + 'constrainteq.cpp',
src_path + 'constraint.cpp',
src_path + 'system.cpp',
src_path + 'lib.cpp',
]
if system() == 'Windows':
# Avoid compile error with CYTHON_USE_PYLONG_INTERNALS.
# https://github.com/cython/cython/issues/2670#issuecomment-432212671
macros.append(('MS_WIN64', None))
# Disable format warning
compile_args.append('-Wno-format')
# Solvespace arguments
macros.append(('WIN32', None))
# Platform sources
sources.append(platform_path + 'w32util.cpp')
sources.append(platform_path + 'platform.cpp')
else:
sources.append(platform_path + 'unixutil.cpp')
setup(
name="python_solvespace",
version=get_version('..', 'CMakeLists.txt'),
author=__author__,
author_email=__email__,
description="Python library of Solvespace",
long_description=read("README.md"),
url="https://github.com/solvespace/solvespace",
packages=find_packages(exclude=('tests',)),
ext_modules=[Extension(
"slvs",
sources,
language="c++",
include_dirs=[include_path, src_path, platform_path],
define_macros=macros,
extra_compile_args=compile_args
)],
python_requires=">=3.6",
setup_requires=[
'setuptools',
'wheel',
'cython',
],
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Cython",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Operating System :: OS Independent",
]
)

1
cython/tests/__init__.py Normal file
View File

@ -0,0 +1 @@

248
cython/tests/test_slvs.py Normal file
View File

@ -0,0 +1,248 @@
# -*- coding: utf-8 -*-
"""This module will test the functions of Python-Solvespace."""
__author__ = "Yuan Chang"
__copyright__ = "Copyright (C) 2016-2019"
__license__ = "GPLv3+"
__email__ = "pyslvs@gmail.com"
import unittest
from unittest import TestCase
from math import radians
from slvs import ResultFlag, SolverSystem, make_quaternion
class CoreTest(TestCase):
def test_crank_rocker(self):
"""Crank rocker example."""
sys = SolverSystem()
wp = sys.create_2d_base()
p0 = sys.add_point_2d(0, 0, wp)
sys.dragged(p0, wp)
p1 = sys.add_point_2d(90, 0, wp)
sys.dragged(p1, wp)
line0 = sys.add_line_2d(p0, p1, wp)
p2 = sys.add_point_2d(20, 20, wp)
p3 = sys.add_point_2d(0, 10, wp)
p4 = sys.add_point_2d(30, 20, wp)
sys.distance(p2, p3, 40, wp)
sys.distance(p2, p4, 40, wp)
sys.distance(p3, p4, 70, wp)
sys.distance(p0, p3, 35, wp)
sys.distance(p1, p4, 70, wp)
line1 = sys.add_line_2d(p0, p3, wp)
sys.angle(line0, line1, 45, wp)
result_flag = sys.solve()
self.assertEqual(result_flag, ResultFlag.OKAY)
x, y = sys.params(p2.params)
self.assertAlmostEqual(39.54852, x, 4)
self.assertAlmostEqual(61.91009, y, 4)
def test_involute(self):
"""Involute example."""
r = 10
angle = 45
sys = SolverSystem()
wp = sys.create_2d_base()
p0 = sys.add_point_2d(0, 0, wp)
sys.dragged(p0, wp)
p1 = sys.add_point_2d(0, 10, wp)
sys.distance(p0, p1, r, wp)
line0 = sys.add_line_2d(p0, p1, wp)
p2 = sys.add_point_2d(10, 10, wp)
line1 = sys.add_line_2d(p1, p2, wp)
sys.distance(p1, p2, r * radians(angle), wp)
sys.perpendicular(line0, line1, wp, False)
p3 = sys.add_point_2d(10, 0, wp)
sys.dragged(p3, wp)
line_base = sys.add_line_2d(p0, p3, wp)
sys.angle(line0, line_base, angle, wp)
result_flag = sys.solve()
self.assertEqual(result_flag, ResultFlag.OKAY)
x, y = sys.params(p2.params)
self.assertAlmostEqual(12.62467, x, 4)
self.assertAlmostEqual(1.51746, y, 4)
def test_jansen_linkage(self):
"""Jansen's linkage example."""
sys = SolverSystem()
wp = sys.create_2d_base()
p0 = sys.add_point_2d(0, 0, wp)
sys.dragged(p0, wp)
p1 = sys.add_point_2d(0, 20, wp)
sys.distance(p0, p1, 15, wp)
line0 = sys.add_line_2d(p0, p1, wp)
p2 = sys.add_point_2d(-38, -7.8, wp)
sys.dragged(p2, wp)
p3 = sys.add_point_2d(-50, 30, wp)
p4 = sys.add_point_2d(-70, -15, wp)
sys.distance(p2, p3, 41.5, wp)
sys.distance(p3, p4, 55.8, wp)
sys.distance(p2, p4, 40.1, wp)
p5 = sys.add_point_2d(-50, -50, wp)
p6 = sys.add_point_2d(-10, -90, wp)
p7 = sys.add_point_2d(-20, -40, wp)
sys.distance(p5, p6, 65.7, wp)
sys.distance(p6, p7, 49.0, wp)
sys.distance(p5, p7, 36.7, wp)
sys.distance(p1, p3, 50, wp)
sys.distance(p1, p7, 61.9, wp)
p8 = sys.add_point_2d(20, 0, wp)
line_base = sys.add_line_2d(p0, p8, wp)
sys.angle(line0, line_base, 45, wp)
result_flag = sys.solve()
self.assertEqual(result_flag, ResultFlag.OKAY)
x, y = sys.params(p2.params)
self.assertAlmostEqual(-38, x, 4)
self.assertAlmostEqual(-7.8, y, 4)
def test_nut_cracker(self):
"""Nut cracker example."""
h0 = 0.5
b0 = 0.75
r0 = 0.25
n1 = 1.5
n2 = 2.3
l0 = 3.25
sys = SolverSystem()
wp = sys.create_2d_base()
p0 = sys.add_point_2d(0, 0, wp)
sys.dragged(p0, wp)
p1 = sys.add_point_2d(2, 2, wp)
p2 = sys.add_point_2d(2, 0, wp)
line0 = sys.add_line_2d(p0, p2, wp)
sys.horizontal(line0, wp)
line1 = sys.add_line_2d(p1, p2, wp)
p3 = sys.add_point_2d(b0 / 2, h0, wp)
sys.dragged(p3, wp)
sys.distance(p3, line1, r0, wp)
sys.distance(p0, p1, n1, wp)
sys.distance(p1, p2, n2, wp)
result_flag = sys.solve()
self.assertEqual(result_flag, ResultFlag.OKAY)
x, _ = sys.params(p2.params)
ans_min = x - b0 / 2
ans_max = l0 - r0 - b0 / 2
self.assertAlmostEqual(1.01576, ans_min, 4)
self.assertAlmostEqual(2.625, ans_max, 4)
def test_pydemo(self):
"""
Some sample code for slvs.dll. We draw some geometric entities, provide
initial guesses for their positions, and then constrain them. The solver
calculates their new positions, in order to satisfy the constraints.
Copyright 2008-2013 Jonathan Westhues.
Copyright 2016-2017 Yuan Chang [pyslvs@gmail.com] Python-Solvespace bundled.
An example of a constraint in 2d. In our first group, we create a workplane
along the reference frame's xy plane. In a second group, we create some
entities in that group and dimension them.
"""
sys = SolverSystem()
sys.set_group(1)
# First, we create our workplane. Its origin corresponds to the origin
# of our base frame (x y z) = (0 0 0)
p101 = sys.add_point_3d(0, 0, 0)
# and it is parallel to the xy plane, so it has basis vectors (1 0 0)
# and (0 1 0).
qw, qx, qy, qz = make_quaternion(1, 0, 0, 0, 1, 0)
n102 = sys.add_normal_3d(qw, qx, qy, qz)
wp200 = sys.add_work_plane(p101, n102)
# Now create a second group. We'll solve group 2, while leaving group 1
# constant; so the workplane that we've created will be locked down,
# and the solver can't move it.
sys.set_group(2)
p301 = sys.add_point_2d(10, 20, wp200)
p302 = sys.add_point_2d(20, 10, wp200)
# And we create a line segment with those endpoints.
l400 = sys.add_line_2d(p301, p302, wp200)
# Now three more points.
p303 = sys.add_point_2d(100, 120, wp200)
p304 = sys.add_point_2d(120, 110, wp200)
p305 = sys.add_point_2d(115, 115, wp200)
# And arc, centered at point 303, starting at point 304, ending at
# point 305.
a401 = sys.add_arc(n102, p303, p304, p305, wp200)
# Now one more point, and a distance
p306 = sys.add_point_2d(200, 200, wp200)
d307 = sys.add_distance(30, wp200)
# And a complete circle, centered at point 306 with radius equal to
# distance 307. The normal is 102, the same as our workplane.
c402 = sys.add_circle(n102, p306, d307, wp200)
# The length of our line segment is 30.0 units.
sys.distance(p301, p302, 30, wp200)
# And the distance from our line segment to the origin is 10.0 units.
sys.distance(p101, l400, 10, wp200)
# And the line segment is vertical.
sys.vertical(l400, wp200)
# And the distance from one endpoint to the origin is 15.0 units.
sys.distance(p301, p101, 15, wp200)
# The arc and the circle have equal radius.
sys.equal(a401, c402, wp200)
# The arc has radius 17.0 units.
sys.diameter(a401, 17 * 2, wp200)
# If the solver fails, then ask it to report which constraints caused
# the problem.
# And solve.
result_flag = sys.solve()
self.assertEqual(result_flag, ResultFlag.OKAY)
x, y = sys.params(p301.params)
self.assertAlmostEqual(10, x, 4)
self.assertAlmostEqual(11.18030, y, 4)
x, y = sys.params(p302.params)
self.assertAlmostEqual(10, x, 4)
self.assertAlmostEqual(-18.81966, y, 4)
x, y = sys.params(p303.params)
self.assertAlmostEqual(101.11418, x, 4)
self.assertAlmostEqual(119.04153, y, 4)
x, y = sys.params(p304.params)
self.assertAlmostEqual(116.47661, x, 4)
self.assertAlmostEqual(111.76171, y, 4)
x, y = sys.params(p305.params)
self.assertAlmostEqual(117.40922, x, 4)
self.assertAlmostEqual(114.19676, y, 4)
x, y = sys.params(p306.params)
self.assertAlmostEqual(200, x, 4)
self.assertAlmostEqual(200, y, 4)
x, = sys.params(d307.params)
self.assertAlmostEqual(17, x, 4)
self.assertEqual(6, sys.dof())
if __name__ == '__main__':
unittest.main()