From 14e28827eeb1129a4e0ee85f1d28b4cad3d05367 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 28 May 2019 20:00:15 +0800 Subject: [PATCH 001/118] Add cython modules. --- cython/README.md | 40 ++ cython/platform/patch.diff | 26 + cython/python_solvespace/__init__.py | 34 ++ cython/python_solvespace/slvs.pxd | 325 +++++++++++ cython/python_solvespace/slvs.pyi | 347 ++++++++++++ cython/python_solvespace/slvs.pyx | 796 +++++++++++++++++++++++++++ cython/requirements.txt | 1 + cython/setup.py | 116 ++++ cython/tests/__init__.py | 1 + cython/tests/test_slvs.py | 248 +++++++++ 10 files changed, 1934 insertions(+) create mode 100644 cython/README.md create mode 100644 cython/platform/patch.diff create mode 100644 cython/python_solvespace/__init__.py create mode 100644 cython/python_solvespace/slvs.pxd create mode 100644 cython/python_solvespace/slvs.pyi create mode 100644 cython/python_solvespace/slvs.pyx create mode 100644 cython/requirements.txt create mode 100644 cython/setup.py create mode 100644 cython/tests/__init__.py create mode 100644 cython/tests/test_slvs.py diff --git a/cython/README.md b/cython/README.md new file mode 100644 index 000000000..08aeba9d0 --- /dev/null +++ b/cython/README.md @@ -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/ diff --git a/cython/platform/patch.diff b/cython/platform/patch.diff new file mode 100644 index 000000000..6b2726dc4 --- /dev/null +++ b/cython/platform/patch.diff @@ -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) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py new file mode 100644 index 000000000..77adc7c37 --- /dev/null +++ b/cython/python_solvespace/__init__.py @@ -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', +] diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd new file mode 100644 index 000000000..7208fcd87 --- /dev/null +++ b/cython/python_solvespace/slvs.pxd @@ -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 = *) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi new file mode 100644 index 000000000..2a5b80d74 --- /dev/null +++ b/cython/python_solvespace/slvs.pyi @@ -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.""" + ... diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx new file mode 100644 index 000000000..586b15cb2 --- /dev/null +++ b/cython/python_solvespace/slvs.pyx @@ -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(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 = self.h + cdef int g = self.g + cdef str t = _NAME_OF_ENTITIES[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 = g + + cpdef int group(self): + """Return the current group by integer.""" + return 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(error) + return failed_list + + cpdef int solve(self): + """Solve the system.""" + # 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 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 = 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 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 = 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}") diff --git a/cython/requirements.txt b/cython/requirements.txt new file mode 100644 index 000000000..f6629e024 --- /dev/null +++ b/cython/requirements.txt @@ -0,0 +1 @@ +cython diff --git a/cython/setup.py b/cython/setup.py new file mode 100644 index 000000000..dd44e7c86 --- /dev/null +++ b/cython/setup.py @@ -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", + ] +) diff --git a/cython/tests/__init__.py b/cython/tests/__init__.py new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/cython/tests/__init__.py @@ -0,0 +1 @@ + diff --git a/cython/tests/test_slvs.py b/cython/tests/test_slvs.py new file mode 100644 index 000000000..6e78a1d49 --- /dev/null +++ b/cython/tests/test_slvs.py @@ -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() From 50a16dfde6c62599013847887a68c68ed1522785 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 29 May 2019 14:34:49 +0800 Subject: [PATCH 002/118] Compatibility adjustment. --- cython/.gitignore | 20 +++++++++++++++++++ cython/python_solvespace/__init__.pxd | 1 + cython/setup.py | 28 ++++++++++++++++++++++++--- src/platform/platform.cpp | 2 +- 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100644 cython/.gitignore create mode 100644 cython/python_solvespace/__init__.pxd diff --git a/cython/.gitignore b/cython/.gitignore new file mode 100644 index 000000000..61c8abbde --- /dev/null +++ b/cython/.gitignore @@ -0,0 +1,20 @@ +slvs.py +python_solvespace/*.cpp +*.o* +*.def +*.lib +*.so +*.pyd +*.a +*.cxx + +*__pycache__* +*obj* + +*.eric6project* +*.e4p +*.e4q +*.e6t +*.ui + +.DS_Store diff --git a/cython/python_solvespace/__init__.pxd b/cython/python_solvespace/__init__.pxd new file mode 100644 index 000000000..8d1c8b69c --- /dev/null +++ b/cython/python_solvespace/__init__.pxd @@ -0,0 +1 @@ + diff --git a/cython/setup.py b/cython/setup.py index dd44e7c86..e83f34ae2 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -10,7 +10,9 @@ import os import re import codecs +from textwrap import dedent from setuptools import setup, Extension, find_packages +from setuptools.command.build_ext import build_ext from platform import system from distutils import sysconfig @@ -22,6 +24,11 @@ lib = sysconfig.get_config_var('BINDIR') +def write(doc, *parts): + with codecs.open(os.path.join(here, *parts), 'w') as f: + f.write(doc) + + def read(*parts): with codecs.open(os.path.join(here, *parts), 'r') as f: return f.read() @@ -77,10 +84,24 @@ def get_version(*file_paths): macros.append(('WIN32', None)) # Platform sources - sources.append(platform_path + 'w32util.cpp') + sources.append(platform_path + 'utilwin.cpp') sources.append(platform_path + 'platform.cpp') else: - sources.append(platform_path + 'unixutil.cpp') + sources.append(platform_path + 'utilunix.cpp') + + +class Build(build_ext): + def run(self): + # Generate "config.h", actually not used. + config_h = src_path + "config.h" + write(dedent(f"""\ + #ifndef SOLVESPACE_CONFIG_H + #define SOLVESPACE_CONFIG_H + #endif + """), config_h) + super(Build, self).run() + os.remove(config_h) + setup( name="python_solvespace", @@ -92,13 +113,14 @@ def get_version(*file_paths): url="https://github.com/solvespace/solvespace", packages=find_packages(exclude=('tests',)), ext_modules=[Extension( - "slvs", + "python_solvespace.slvs", sources, language="c++", include_dirs=[include_path, src_path, platform_path], define_macros=macros, extra_compile_args=compile_args )], + cmdclass={'build_ext': Build}, python_requires=">=3.6", setup_requires=[ 'setuptools', diff --git a/src/platform/platform.cpp b/src/platform/platform.cpp index ab08828db..80ffd7f39 100644 --- a/src/platform/platform.cpp +++ b/src/platform/platform.cpp @@ -458,7 +458,7 @@ bool WriteFile(const Platform::Path &filename, const std::string &data) { #if defined(WIN32) const void *LoadResource(const std::string &name, size_t *size) { - HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA); + HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), (LPWSTR)RT_RCDATA); ssassert(hres != NULL, "Cannot find resource"); HGLOBAL res = ::LoadResource(NULL, hres); ssassert(res != NULL, "Cannot load resource"); From 5be39f2ba15c9382afcc78aed3a13aef6664b083 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 29 May 2019 15:30:17 +0800 Subject: [PATCH 003/118] Update ignore items. --- cython/.gitignore | 121 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 12 deletions(-) diff --git a/cython/.gitignore b/cython/.gitignore index 61c8abbde..612cc2db7 100644 --- a/cython/.gitignore +++ b/cython/.gitignore @@ -1,20 +1,117 @@ -slvs.py +# cython python_solvespace/*.cpp + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so *.o* +*.cxx *.def *.lib -*.so -*.pyd -*.a -*.cxx -*__pycache__* -*obj* +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +AppImageAssistant + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ +out/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ -*.eric6project* -*.e4p -*.e4q -*.e6t -*.ui +#PyCharm cache +.idea/ +# Others .DS_Store +*/.DS_Store From 329c1246ae2626dfc82e5a754aa45d7f27e1111e Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 29 May 2019 15:52:08 +0800 Subject: [PATCH 004/118] Update test case module. --- cython/README.md | 2 +- cython/tests/{test_slvs.py => __main__.py} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename cython/tests/{test_slvs.py => __main__.py} (99%) diff --git a/cython/README.md b/cython/README.md index 08aeba9d0..a2c749e73 100644 --- a/cython/README.md +++ b/cython/README.md @@ -27,7 +27,7 @@ python setup.py install --user # User mode Run unit test: ```bash -python tests/test_slvs.py +python tests ``` Uninstall the module: diff --git a/cython/tests/test_slvs.py b/cython/tests/__main__.py similarity index 99% rename from cython/tests/test_slvs.py rename to cython/tests/__main__.py index 6e78a1d49..7f17096e9 100644 --- a/cython/tests/test_slvs.py +++ b/cython/tests/__main__.py @@ -10,7 +10,7 @@ import unittest from unittest import TestCase from math import radians -from slvs import ResultFlag, SolverSystem, make_quaternion +from python_solvespace import ResultFlag, SolverSystem, make_quaternion class CoreTest(TestCase): From caaed1129079a13195cbde3ea601695fb7ac348f Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 29 May 2019 16:34:37 +0800 Subject: [PATCH 005/118] Constrain Cython version. --- cython/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/requirements.txt b/cython/requirements.txt index f6629e024..3426b3a26 100644 --- a/cython/requirements.txt +++ b/cython/requirements.txt @@ -1 +1 @@ -cython +cython<=0.29.8 From 0975723a3f565a83c2fdaaf7db0d5226a13ada5e Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 29 May 2019 17:44:55 +0800 Subject: [PATCH 006/118] Add stub file to package data. --- cython/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cython/setup.py b/cython/setup.py index e83f34ae2..a980f1acc 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -112,6 +112,7 @@ def run(self): long_description=read("README.md"), url="https://github.com/solvespace/solvespace", packages=find_packages(exclude=('tests',)), + package_data={'': ["*.pyi"]}, ext_modules=[Extension( "python_solvespace.slvs", sources, From 5fe420bdff7884de83daad298910212570880df5 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 8 Jul 2019 16:37:12 +0800 Subject: [PATCH 007/118] Correction of unittest case. --- cython/tests/__main__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cython/tests/__main__.py b/cython/tests/__main__.py index 7f17096e9..0f44f8a38 100644 --- a/cython/tests/__main__.py +++ b/cython/tests/__main__.py @@ -107,9 +107,9 @@ def test_jansen_linkage(self): 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) + x, y = sys.params(p8.params) + self.assertAlmostEqual(18.93036, x, 4) + self.assertAlmostEqual(13.63778, y, 4) def test_nut_cracker(self): """Nut cracker example.""" From 594ae254832d7618f7285c2c15ec8216601bd530 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 9 Jul 2019 15:51:20 +0800 Subject: [PATCH 008/118] Fix URL. --- cython/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/README.md b/cython/README.md index a2c749e73..0c9865dff 100644 --- a/cython/README.md +++ b/cython/README.md @@ -8,7 +8,7 @@ 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). +Feature for CDemo and Python interface can see [here](https://github.com/solvespace/solvespace/blob/master/exposed/DOC.txt). Build and Test === From d5ac4039eac70d2fa85bf2ca9cb8a024bbbf0912 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Thu, 11 Jul 2019 20:40:33 +0800 Subject: [PATCH 009/118] Add set values method for parameters. --- cython/python_solvespace/slvs.pxd | 1 + cython/python_solvespace/slvs.pyi | 5 ++++- cython/python_solvespace/slvs.pyx | 13 +++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index 7208fcd87..92f01762d 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -268,6 +268,7 @@ cdef class SolverSystem: cdef void free(self) cpdef void set_group(self, size_t g) cpdef int group(self) + cpdef void set_params(self, Params p, object params) cpdef tuple params(self, Params p) cpdef int dof(self) cpdef object constraints(self) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 2a5b80d74..868726eed 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Tuple, List, Counter +from typing import Tuple, List, Sequence, Counter from enum import IntEnum, auto @@ -164,6 +164,9 @@ class SolverSystem: def group(self) -> int: ... + def set_params(self, p: Params, params: Sequence[float]): + ... + def params(self, p: Params) -> Tuple[float, ...]: ... diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 586b15cb2..a2dfbe85a 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -309,6 +309,19 @@ cdef class SolverSystem: """Return the current group by integer.""" return self.g + cpdef void set_params(self, Params p, object params): + """Set the parameters by Params object and sequence object.""" + 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 by Params object.""" cdef list param_list = [] From a5d120520edc199e71ecac394a5b19429b06ebe0 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 30 Aug 2019 22:23:52 +0800 Subject: [PATCH 010/118] Remove --user option. --- cython/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/cython/README.md b/cython/README.md index 0c9865dff..65cebb902 100644 --- a/cython/README.md +++ b/cython/README.md @@ -21,7 +21,6 @@ Build and install the module: ```bash python setup.py install -python setup.py install --user # User mode ``` Run unit test: From dcd2b1385a2ad021ef120ff3289c3dd5f09e35aa Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 31 Aug 2019 09:57:49 +0800 Subject: [PATCH 011/118] Input requirements from the pip list. --- cython/requirements.txt | 4 +++- cython/setup.py | 17 +++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/cython/requirements.txt b/cython/requirements.txt index 3426b3a26..d1e61c4a5 100644 --- a/cython/requirements.txt +++ b/cython/requirements.txt @@ -1 +1,3 @@ -cython<=0.29.8 +setuptools +wheel +cython diff --git a/cython/setup.py b/cython/setup.py index a980f1acc..aba3f1897 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -8,6 +8,11 @@ __email__ = "pyslvs@gmail.com" import os +from os.path import ( + abspath, + dirname, + join as pth_join, +) import re import codecs from textwrap import dedent @@ -16,7 +21,7 @@ from platform import system from distutils import sysconfig -here = os.path.abspath(os.path.dirname(__file__)) +here = abspath(dirname(__file__)) include_path = '../include/' src_path = '../src/' platform_path = src_path + 'platform/' @@ -25,12 +30,12 @@ def write(doc, *parts): - with codecs.open(os.path.join(here, *parts), 'w') as f: + with codecs.open(pth_join(here, *parts), 'w') as f: f.write(doc) def read(*parts): - with codecs.open(os.path.join(here, *parts), 'r') as f: + with codecs.open(pth_join(here, *parts), 'r') as f: return f.read() @@ -123,11 +128,7 @@ def run(self): )], cmdclass={'build_ext': Build}, python_requires=">=3.6", - setup_requires=[ - 'setuptools', - 'wheel', - 'cython', - ], + setup_requires=read('requirements.txt').splitlines(), classifiers=[ "Programming Language :: Python :: 3", "Programming Language :: Cython", From 2606bfeb8f6585522763ca620dcbc6534abfe5e6 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 6 Sep 2019 12:43:52 +0800 Subject: [PATCH 012/118] Add test suit option of "setup.py". --- cython/setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cython/setup.py b/cython/setup.py index aba3f1897..d534afc03 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -129,6 +129,7 @@ def run(self): cmdclass={'build_ext': Build}, python_requires=">=3.6", setup_requires=read('requirements.txt').splitlines(), + test_suite="tests", classifiers=[ "Programming Language :: Python :: 3", "Programming Language :: Cython", From 85e6277d2f056c75ff25e36b3162ef547fa1d059 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 6 Sep 2019 22:18:11 +0800 Subject: [PATCH 013/118] Add return None annotations. --- cython/python_solvespace/slvs.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 868726eed..7a40a01c3 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -152,7 +152,7 @@ class Entity: class SolverSystem: - def __init__(self): + def __init__(self) -> None: ... def clear(self) -> None: @@ -164,7 +164,7 @@ class SolverSystem: def group(self) -> int: ... - def set_params(self, p: Params, params: Sequence[float]): + def set_params(self, p: Params, params: Sequence[float]) -> None: ... def params(self, p: Params) -> Tuple[float, ...]: From d6dc4eaf6e7c2d06f9e44d2f0062f3555cf8c4c6 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 10 Sep 2019 15:34:13 +0800 Subject: [PATCH 014/118] Update stub files. --- cython/python_solvespace/slvs.pyi | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 7a40a01c3..ef6208bd7 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from typing import Tuple, List, Sequence, Counter +from typing import Tuple, List, Sequence, Counter, ClassVar from enum import IntEnum, auto @@ -93,9 +93,8 @@ class Params: class Entity: - FREE_IN_3D: Entity - NONE: Entity - + FREE_IN_3D: ClassVar[Entity] + NONE: ClassVar[Entity] params: Params def is_3d(self) -> bool: From 1c18ca015ef4ae934f0ac53ad219974cb8c6b159 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 10 Sep 2019 17:34:12 +0800 Subject: [PATCH 015/118] Replace "__class__" with "type()". --- cython/python_solvespace/slvs.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index a2dfbe85a..bbd8a942c 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -54,7 +54,7 @@ cdef class Params: return params def __repr__(self) -> str: - cdef str m = f"{self.__class__.__name__}([" + cdef str m = f"{type(self).__name__}([" cdef size_t i cdef size_t s = self.param_list.size() for i in range(s): @@ -227,7 +227,7 @@ cdef class Entity: cdef int g = self.g cdef str t = _NAME_OF_ENTITIES[self.t] return ( - f"{self.__class__.__name__}" + f"{type(self).__name__}" f"(handle={h}, group={g}, type=<{t}>, is_3d={self.is_3d()}, params={self.params})" ) From 88201db0eaa5b98843196d1d0f5b63cecae9a3ca Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 13 Sep 2019 14:22:10 +0800 Subject: [PATCH 016/118] Remove "setup_requires" option. --- cython/setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/cython/setup.py b/cython/setup.py index d534afc03..158b8460f 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -128,14 +128,12 @@ def run(self): )], cmdclass={'build_ext': Build}, python_requires=">=3.6", - setup_requires=read('requirements.txt').splitlines(), + install_requires=read('requirements.txt').splitlines(), test_suite="tests", 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", ] ) From c05df29b3b8d87d492353269f8fd93160e9ebed0 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 20 Sep 2019 16:36:21 +0800 Subject: [PATCH 017/118] Adjust CI config as matrix. --- .travis.yml | 18 ++++++++++++------ appveyor.yml | 28 ++++++++++++++++------------ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 93b331bc4..f7dc8d471 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,10 +1,15 @@ language: c -os: - - linux - - osx -sudo: required -dist: trusty -osx_image: xcode8.2 + +matrix: + include: + + - os: linux + sudo: required + dist: trusty + + - os: osx + osx_image: xcode8.2 + install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./.travis/install-debian.sh; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./.travis/install-macos.sh; fi @@ -12,6 +17,7 @@ script: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./.travis/build-debian.sh; fi # the awk command is a workaround for https://github.com/travis-ci/travis-ci/issues/4704. - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./.travis/build-macos.sh | awk '/.{0,32}/ {print $0}'; fi + deploy: - provider: releases api_key: diff --git a/appveyor.yml b/appveyor.yml index bb4c6d9b2..8865d1a66 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,17 +1,20 @@ version: '{build}' clone_depth: 1 -before_build: - - git submodule update --init - - set tag=x%APPVEYOR_REPO_TAG_NAME% - - if %tag:~,2% == xv (set BUILD_TYPE=RelWithDebInfo) else (set BUILD_TYPE=Debug) - - mkdir build - - cmake -G"Visual Studio 12" -Tv120 -Bbuild -H. -build_script: - - msbuild "build\src\solvespace.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - - msbuild "build\src\solvespace-cli.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - - msbuild "build\test\solvespace-testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" -test_script: - - build\bin\%BUILD_TYPE%\solvespace-testsuite.exe +for: + - image: Visual Studio 2013 + before_build: + - git submodule update --init + - set tag=x%APPVEYOR_REPO_TAG_NAME% + - if %tag:~,2% == xv (set BUILD_TYPE=RelWithDebInfo) else (set BUILD_TYPE=Debug) + - mkdir build + - cmake -G"Visual Studio 12" -Tv120 -Bbuild -H. + build_script: + - msbuild "build\src\solvespace.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - msbuild "build\src\solvespace-cli.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - msbuild "build\test\solvespace-testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + test_script: + - build\bin\%BUILD_TYPE%\solvespace-testsuite.exe + artifacts: - path: build\bin\%BUILD_TYPE%\solvespace.exe name: solvespace.exe @@ -19,6 +22,7 @@ artifacts: name: solvespace-cli.exe - path: build\bin\%BUILD_TYPE%\solvespace.pdb name: solvespace.pdb + deploy: - provider: GitHub auth_token: From cfe0c75f3014b30a721a3f0636ac8832b4eb6c12 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 23 Sep 2019 10:30:20 +0800 Subject: [PATCH 018/118] Update deployment config. --- .travis.yml | 106 +++++++++++++++++++++------ appveyor.yml | 3 +- cython/python_solvespace/__init__.py | 1 + cython/setup.py | 12 ++- 4 files changed, 93 insertions(+), 29 deletions(-) diff --git a/.travis.yml b/.travis.yml index f7dc8d471..e083cf57a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -language: c +language: python matrix: include: @@ -6,25 +6,89 @@ matrix: - os: linux sudo: required dist: trusty + language: c + install: + - ./.travis/install-debian.sh + script: + - ./.travis/build-debian.sh - os: osx - osx_image: xcode8.2 - -install: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./.travis/install-debian.sh; fi - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./.travis/install-macos.sh; fi -script: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./.travis/build-debian.sh; fi - # the awk command is a workaround for https://github.com/travis-ci/travis-ci/issues/4704. - - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ./.travis/build-macos.sh | awk '/.{0,32}/ {print $0}'; fi - -deploy: - - provider: releases - api_key: - secure: dDlkIawHcODlW9B/20/cQCtzeoocvs0hKuNngRKXKqzXLWTRq33oq/B7+39tAixWbmv6exTpijiKrRNFiSCW5Z4iwHLwaRD4XJznxw63e/Hus/dxg2Tvqx7XFpkCz8mT1Z+gZQE5YxAngeZPpI/sZbZtF1UO3yH5eLeeokZ15p26ZskQUPoYuzrTgTzYL3XfpG3F+20rNBawH1ycsCTVD/08/n31d2m3CrKAsbW7er92ek6w4fzKr7NW8WeXjrPJETVpw5fQg1Od3pRGW8dPQaJcvKQEogMp8Mm0ETYd0qigg89/giBz7QwOgmAWQ4dH+DfZH4Ojl//127QztBolMvyDMQBykWrtJoGcij05sT6K2IJr2FHeUBO12MAEdjiVvhQj3DtTzjPiZAHHDBSLWxLKWWhlhHE4pq7g1MQhqXkaAHI2BLNzwLmaowbMT0bECf9yfz6xx18h6XPQFX44oOktraobVALFlyHqeKa8zdcUt22LF6uAL1m5dxL0tny3eXCIPE4UH/RZgua/cHV9G3cUvKQa/QnFSLRhvWVSbGB+7YsHouBJcsUOOW1gmd5442XuC7mpppccRldh+GSxUk6TBJRAx7TeQ0ybDUaoco9MUqp2twv3KreR2+8Q12PDaAhfQVNEGdF3wTm1sShImjCN4VN3eSLlBEbve1QRQXM= - skip_cleanup: true - file: build/SolveSpace.dmg - on: - repo: solvespace/solvespace - tags: true - condition: "$TRAVIS_OS_NAME == osx" + osx_image: xcode8.3 + language: c + install: + - ./.travis/install-macos.sh + script: + # the awk command is a workaround for https://github.com/travis-ci/travis-ci/issues/4704. + - ./.travis/build-macos.sh | awk '/.{0,32}/ {print $0}' + deploy: + - provider: releases + api_key: + secure: dDlkIawHcODlW9B/20/cQCtzeoocvs0hKuNngRKXKqzXLWTRq33oq/B7+39tAixWbmv6exTpijiKrRNFiSCW5Z4iwHLwaRD4XJznxw63e/Hus/dxg2Tvqx7XFpkCz8mT1Z+gZQE5YxAngeZPpI/sZbZtF1UO3yH5eLeeokZ15p26ZskQUPoYuzrTgTzYL3XfpG3F+20rNBawH1ycsCTVD/08/n31d2m3CrKAsbW7er92ek6w4fzKr7NW8WeXjrPJETVpw5fQg1Od3pRGW8dPQaJcvKQEogMp8Mm0ETYd0qigg89/giBz7QwOgmAWQ4dH+DfZH4Ojl//127QztBolMvyDMQBykWrtJoGcij05sT6K2IJr2FHeUBO12MAEdjiVvhQj3DtTzjPiZAHHDBSLWxLKWWhlhHE4pq7g1MQhqXkaAHI2BLNzwLmaowbMT0bECf9yfz6xx18h6XPQFX44oOktraobVALFlyHqeKa8zdcUt22LF6uAL1m5dxL0tny3eXCIPE4UH/RZgua/cHV9G3cUvKQa/QnFSLRhvWVSbGB+7YsHouBJcsUOOW1gmd5442XuC7mpppccRldh+GSxUk6TBJRAx7TeQ0ybDUaoco9MUqp2twv3KreR2+8Q12PDaAhfQVNEGdF3wTm1sShImjCN4VN3eSLlBEbve1QRQXM= + skip_cleanup: true + file: build/SolveSpace.dmg + on: + repo: solvespace/solvespace + tags: true + + - &linux + os: linux + sudo: required + dist: xenial + python: "3.6" + install: &python-install + - cd cython && python3 -m pip install -r requirements.txt && cd - + script: &python-script + - cd cython && python3 setup.py test && cd - + deploy: + provider: pypi + user: $TWINE_USERNAME + password: $TWINE_PASSWORD + skip_cleanup: true + skip_existing: true + file: cython/dist/*.whl + on: + repo: KmolYuan/solvespace + tags: true + + - <<: *linux + python: "3.7" + + - <<: *linux + python: "3.8-dev" + + - &osx + os: osx + osx_image: xcode10 + language: generic + env: PYTHON=3.6.0 + before_install: + - brew update + - brew upgrade pyenv + - export PATH="/Users/travis/.pyenv/shims:${PATH}" + - pyenv install ${PYTHON} + - pyenv global ${PYTHON} + - python3 -m pip install pip -U + - python3 --version + - python3 -m pip --version + install: *python-install + script: *python-script + after_success: + # PyPI deployment + - if [ -n "$TRAVIS_TAG" ]; then + python3 -m pip install twine; + python3 setup.py sdist bdist_wheel; + python3 -m twine upload cython/dist/*.whl --skip-existing; + fi + + - <<: *osx + env: PYTHON=3.7.0 + + - <<: *osx + env: PYTHON=3.8-dev + +before_cache: + - rm -rf $HOME/.cache/pip/log + +cache: + directories: + - $HOME/.cache/pip diff --git a/appveyor.yml b/appveyor.yml index 8865d1a66..560492c17 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -30,4 +30,5 @@ deploy: description: "" artifact: solvespace.exe,solvespace-cli.exe,solvespace.pdb on: - appveyor_repo_tag: true + APPVEYOR_REPO_NAME: solvespace/solvespace + APPVEYOR_REPO_TAG: true diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 77adc7c37..550943c87 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,6 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" +__version__ = "3.0.0" from .slvs import ( quaternion_u, diff --git a/cython/setup.py b/cython/setup.py index 158b8460f..14b36ed9c 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -39,12 +39,10 @@ def read(*parts): 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)}" +def find_version(*file_paths): + m = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", read(*file_paths), re.M) + if m: + return m.group(1) raise RuntimeError("Unable to find version string.") @@ -110,7 +108,7 @@ def run(self): setup( name="python_solvespace", - version=get_version('..', 'CMakeLists.txt'), + version=find_version('python_solvespace', '__init__.py'), author=__author__, author_email=__email__, description="Python library of Solvespace", From d9abfe3d52c1a5da3c7e706e6c0a17a87664f32b Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 23 Sep 2019 13:28:43 +0800 Subject: [PATCH 019/118] Add Windows build. --- .travis.yml | 7 ++- appveyor.yml | 72 +++++++++++++++++++++++++----- cython/platform/set_pycompiler.bat | 18 ++++++++ 3 files changed, 82 insertions(+), 15 deletions(-) create mode 100644 cython/platform/set_pycompiler.bat diff --git a/.travis.yml b/.travis.yml index e083cf57a..7f05a52d1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,3 @@ -language: python - matrix: include: @@ -34,9 +32,10 @@ matrix: os: linux sudo: required dist: xenial + language: python python: "3.6" install: &python-install - - cd cython && python3 -m pip install -r requirements.txt && cd - + - python3 -m pip install -r cython/requirements.txt script: &python-script - cd cython && python3 setup.py test && cd - deploy: @@ -74,7 +73,7 @@ matrix: script: *python-script after_success: # PyPI deployment - - if [ -n "$TRAVIS_TAG" ]; then + - if [ "$TRAVIS_REPO_SLUG" == "KmolYuan/solvespace" && -n "$TRAVIS_TAG" ]; then python3 -m pip install twine; python3 setup.py sdist bdist_wheel; python3 -m twine upload cython/dist/*.whl --skip-existing; diff --git a/appveyor.yml b/appveyor.yml index 560492c17..6f61c24c4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,17 @@ version: '{build}' clone_depth: 1 +environment: + MSYS_DIR: C:\msys64 + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DIR: C:\Python36-x64 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DIR: C:\Python37-x64 for: - - image: Visual Studio 2013 + - matrix: + only: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 before_build: - git submodule update --init - set tag=x%APPVEYOR_REPO_TAG_NAME% @@ -14,6 +24,55 @@ for: - msbuild "build\test\solvespace-testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" test_script: - build\bin\%BUILD_TYPE%\solvespace-testsuite.exe + deploy: + - provider: GitHub + auth_token: + secure: P9/pf2nM+jlWKe7pCjMp41HycBNP/+5AsmE/TETrDUoBOa/9WFHelqdVFrbRn9IC + description: "" + artifact: solvespace.exe,solvespace-cli.exe,solvespace.pdb + on: + APPVEYOR_REPO_NAME: solvespace/solvespace + APPVEYOR_REPO_TAG: true + + - matrix: + only: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DIR: C:\Python36-x64 + install: &python-install + # Environment variables + - set Path=%MSYS_DIR%\mingw64\bin;%MSYS_DIR%\usr\bin;%Path% + - set Path=%PYTHON_DIR%;%PYTHON_DIR%\Scripts;%Path% + # Show Python + - python --version + - pip --version + # Upgrade setuptools + - pip install setuptools -U + # Set Python compiler to MinGW + - cython\platform\set_pycompiler %PYTHON_DIR% + # Install modules + - pip install -r cython\requirements.txt + # Show tool kits + - gcc --version + - mingw32-make --version + build_script: &python-script + - cd cython && python setup.py test && cd .. + deploy_script: &python-deploy + # PyPI deployment + - IF "%APPVEYOR_REPO_TAG%"=="true" + IF "%APPVEYOR_REPO_NAME%"=="KmolYuan/solvespace" ( + pip install twine && + cd cython && + python setup.py bdist_wheel && + twine upload dist\*.whl --skip-existing + ) + + - matrix: + only: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DIR: C:\Python37-x64 + install: *python-install + build_script: *python-script + deploy_script: *python-deploy artifacts: - path: build\bin\%BUILD_TYPE%\solvespace.exe @@ -22,13 +81,4 @@ artifacts: name: solvespace-cli.exe - path: build\bin\%BUILD_TYPE%\solvespace.pdb name: solvespace.pdb - -deploy: - - provider: GitHub - auth_token: - secure: P9/pf2nM+jlWKe7pCjMp41HycBNP/+5AsmE/TETrDUoBOa/9WFHelqdVFrbRn9IC - description: "" - artifact: solvespace.exe,solvespace-cli.exe,solvespace.pdb - on: - APPVEYOR_REPO_NAME: solvespace/solvespace - APPVEYOR_REPO_TAG: true + - path: cython\dist\*.whl diff --git a/cython/platform/set_pycompiler.bat b/cython/platform/set_pycompiler.bat new file mode 100644 index 000000000..03ec1ff12 --- /dev/null +++ b/cython/platform/set_pycompiler.bat @@ -0,0 +1,18 @@ +echo off + +REM Usage: set_pycompiler C:\Python37 +REM Where %PYTHON_DIR% is the directory of your Python installation. +REM In Pyslvs project. +set HERE=%~dp0 +set PYTHON_DIR=%1 + +REM Create "distutils.cfg" +echo [build]>> "%PYTHON_DIR%\Lib\distutils\distutils.cfg" +echo compiler=mingw32>> "%PYTHON_DIR%\Lib\distutils\distutils.cfg" + +REM Apply the patch of "cygwinccompiler.py". +REM Unix "patch" command of Msys. +patch "%PYTHON_DIR%\lib\distutils\cygwinccompiler.py" "%HERE%patch.diff" + +REM Copy "vcruntime140.dll" to "libs". +copy "%PYTHON_DIR%\vcruntime140.dll" "%PYTHON_DIR%\libs" From da23826f195de8248683a5383fbcea9cfc67ac1a Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 23 Sep 2019 13:28:43 +0800 Subject: [PATCH 020/118] Add Windows build. --- .travis.yml | 8 ++-- appveyor.yml | 72 +++++++++++++++++++++++++----- cython/platform/set_pycompiler.bat | 18 ++++++++ 3 files changed, 83 insertions(+), 15 deletions(-) create mode 100644 cython/platform/set_pycompiler.bat diff --git a/.travis.yml b/.travis.yml index e083cf57a..d2d030a99 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,3 @@ -language: python - matrix: include: @@ -34,9 +32,10 @@ matrix: os: linux sudo: required dist: xenial + language: python python: "3.6" install: &python-install - - cd cython && python3 -m pip install -r requirements.txt && cd - + - python3 -m pip install -r cython/requirements.txt script: &python-script - cd cython && python3 setup.py test && cd - deploy: @@ -74,8 +73,9 @@ matrix: script: *python-script after_success: # PyPI deployment - - if [ -n "$TRAVIS_TAG" ]; then + - if [ "$TRAVIS_REPO_SLUG" == "KmolYuan/solvespace" && -n "$TRAVIS_TAG" ]; then python3 -m pip install twine; + cd cython; python3 setup.py sdist bdist_wheel; python3 -m twine upload cython/dist/*.whl --skip-existing; fi diff --git a/appveyor.yml b/appveyor.yml index 560492c17..84edf7b7f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,7 +1,17 @@ version: '{build}' clone_depth: 1 +environment: + MSYS_DIR: C:\msys64 + matrix: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DIR: C:\Python36-x64 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DIR: C:\Python37-x64 for: - - image: Visual Studio 2013 + - matrix: + only: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 before_build: - git submodule update --init - set tag=x%APPVEYOR_REPO_TAG_NAME% @@ -14,6 +24,55 @@ for: - msbuild "build\test\solvespace-testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" test_script: - build\bin\%BUILD_TYPE%\solvespace-testsuite.exe + deploy: + - provider: GitHub + auth_token: + secure: P9/pf2nM+jlWKe7pCjMp41HycBNP/+5AsmE/TETrDUoBOa/9WFHelqdVFrbRn9IC + description: "" + artifact: solvespace.exe,solvespace-cli.exe,solvespace.pdb + on: + APPVEYOR_REPO_NAME: solvespace/solvespace + APPVEYOR_REPO_TAG: true + + - matrix: + only: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DIR: C:\Python36-x64 + install: &python-install + # Environment variables + - set Path=%MSYS_DIR%\mingw64\bin;%MSYS_DIR%\usr\bin;%Path% + - set Path=%PYTHON_DIR%;%PYTHON_DIR%\Scripts;%Path% + # Show Python + - python --version + - pip --version + # Upgrade setuptools + - pip install setuptools -U + # Set Python compiler to MinGW + - cython\platform\set_pycompiler %PYTHON_DIR% + # Install modules + - pip install -r cython\requirements.txt + # Show tool kits + - gcc --version + - mingw32-make --version + build_script: &python-script + - cd cython && python setup.py test && cd .. + deploy_script: &python-deploy + # PyPI deployment + - IF "%APPVEYOR_REPO_TAG%"=="true" + IF "%APPVEYOR_REPO_NAME%"=="KmolYuan/solvespace" ( + pip install twine && + cd cython && + python setup.py bdist_wheel && + twine upload cython\dist\*.whl --skip-existing + ) + + - matrix: + only: + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DIR: C:\Python37-x64 + install: *python-install + build_script: *python-script + deploy_script: *python-deploy artifacts: - path: build\bin\%BUILD_TYPE%\solvespace.exe @@ -22,13 +81,4 @@ artifacts: name: solvespace-cli.exe - path: build\bin\%BUILD_TYPE%\solvespace.pdb name: solvespace.pdb - -deploy: - - provider: GitHub - auth_token: - secure: P9/pf2nM+jlWKe7pCjMp41HycBNP/+5AsmE/TETrDUoBOa/9WFHelqdVFrbRn9IC - description: "" - artifact: solvespace.exe,solvespace-cli.exe,solvespace.pdb - on: - APPVEYOR_REPO_NAME: solvespace/solvespace - APPVEYOR_REPO_TAG: true + - path: cython\dist\*.whl diff --git a/cython/platform/set_pycompiler.bat b/cython/platform/set_pycompiler.bat new file mode 100644 index 000000000..03ec1ff12 --- /dev/null +++ b/cython/platform/set_pycompiler.bat @@ -0,0 +1,18 @@ +echo off + +REM Usage: set_pycompiler C:\Python37 +REM Where %PYTHON_DIR% is the directory of your Python installation. +REM In Pyslvs project. +set HERE=%~dp0 +set PYTHON_DIR=%1 + +REM Create "distutils.cfg" +echo [build]>> "%PYTHON_DIR%\Lib\distutils\distutils.cfg" +echo compiler=mingw32>> "%PYTHON_DIR%\Lib\distutils\distutils.cfg" + +REM Apply the patch of "cygwinccompiler.py". +REM Unix "patch" command of Msys. +patch "%PYTHON_DIR%\lib\distutils\cygwinccompiler.py" "%HERE%patch.diff" + +REM Copy "vcruntime140.dll" to "libs". +copy "%PYTHON_DIR%\vcruntime140.dll" "%PYTHON_DIR%\libs" From 0b2cfc4201b558852d15171ece3b38da407d31a2 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 23 Sep 2019 21:20:10 +0800 Subject: [PATCH 021/118] Update badges. --- cython/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cython/README.md b/cython/README.md index 65cebb902..bd8b5fdaf 100644 --- a/cython/README.md +++ b/cython/README.md @@ -1,6 +1,7 @@ +[![Version](https://img.shields.io/badge/version-3.0.0-yellow.svg)](https://github.com/KmolYuan/solvespace/releases/latest) [![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) +[![Build status](https://img.shields.io/travis/KmolYuan/solvespace.svg?logo=travis)](https://travis-ci.org/KmolYuan/solvespace) +[![PyPI](https://img.shields.io/pypi/v/python-solvespace.svg)](https://pypi.org/project/python-solvespace/) [![GitHub license](https://img.shields.io/badge/license-GPLv3+-blue.svg)](https://raw.githubusercontent.com/KmolYuan/solvespace/master/LICENSE) python-solvespace From 67a87605db895d57bc0da2519aebf4e3834e253b Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 23 Sep 2019 21:24:08 +0800 Subject: [PATCH 022/118] Fix PyPI readme format. --- .travis.yml | 22 +++++++++++----------- appveyor.yml | 2 +- cython/setup.py | 1 + 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index d2d030a99..24ae152bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,16 +38,16 @@ matrix: - python3 -m pip install -r cython/requirements.txt script: &python-script - cd cython && python3 setup.py test && cd - - deploy: - provider: pypi - user: $TWINE_USERNAME - password: $TWINE_PASSWORD - skip_cleanup: true - skip_existing: true - file: cython/dist/*.whl - on: - repo: KmolYuan/solvespace - tags: true + before_deploy: cd cython + after_success: + # PyPI deployment + - if [[ "$TRAVIS_REPO_SLUG" == "KmolYuan/Pyslvs-UI" && -n "$TRAVIS_TAG" ]]; then + python3 -m pip install auditwheel twine; + python3 setup.py sdist bdist_wheel; + mkdir dist-new; + auditwheel repair dist/*.whl -w dist-new; + python3 -m twine upload dist-new/*.whl --skip-existing; + fi - <<: *linux python: "3.7" @@ -77,7 +77,7 @@ matrix: python3 -m pip install twine; cd cython; python3 setup.py sdist bdist_wheel; - python3 -m twine upload cython/dist/*.whl --skip-existing; + python3 -m twine upload dist/*.whl --skip-existing; fi - <<: *osx diff --git a/appveyor.yml b/appveyor.yml index 84edf7b7f..6f61c24c4 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -63,7 +63,7 @@ for: pip install twine && cd cython && python setup.py bdist_wheel && - twine upload cython\dist\*.whl --skip-existing + twine upload dist\*.whl --skip-existing ) - matrix: diff --git a/cython/setup.py b/cython/setup.py index 14b36ed9c..92b63e0fd 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -113,6 +113,7 @@ def run(self): author_email=__email__, description="Python library of Solvespace", long_description=read("README.md"), + long_description_content_type='text/markdown', url="https://github.com/solvespace/solvespace", packages=find_packages(exclude=('tests',)), package_data={'': ["*.pyi"]}, From 495a285c7ec97a5bfd0373a70789a6d91db3f0b1 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 24 Sep 2019 14:04:44 +0800 Subject: [PATCH 023/118] Fix PyPI deployment. --- .travis.yml | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/.travis.yml b/.travis.yml index 24ae152bc..5b7dca392 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,23 +31,29 @@ matrix: - &linux os: linux sudo: required - dist: xenial + dist: bionic language: python python: "3.6" + addons: + apt: + update: true + packages: + - patchelf install: &python-install - python3 -m pip install -r cython/requirements.txt script: &python-script - cd cython && python3 setup.py test && cd - before_deploy: cd cython - after_success: - # PyPI deployment - - if [[ "$TRAVIS_REPO_SLUG" == "KmolYuan/Pyslvs-UI" && -n "$TRAVIS_TAG" ]]; then - python3 -m pip install auditwheel twine; - python3 setup.py sdist bdist_wheel; - mkdir dist-new; - auditwheel repair dist/*.whl -w dist-new; - python3 -m twine upload dist-new/*.whl --skip-existing; - fi + deploy: + - provider: pypi + user: $TWINE_USERNAME + password: $TWINE_PASSWORD + skip_cleanup: true + skip_existing: true + distributions: sdist + on: + repo: KmolYuan/solvespace + tags: true - <<: *linux python: "3.7" @@ -73,10 +79,10 @@ matrix: script: *python-script after_success: # PyPI deployment - - if [ "$TRAVIS_REPO_SLUG" == "KmolYuan/solvespace" && -n "$TRAVIS_TAG" ]; then + - if [[ "$TRAVIS_REPO_SLUG" == "KmolYuan/solvespace" && -n "$TRAVIS_TAG" ]]; then python3 -m pip install twine; cd cython; - python3 setup.py sdist bdist_wheel; + python3 setup.py bdist_wheel; python3 -m twine upload dist/*.whl --skip-existing; fi From 8c4a37ef4cee396cb2f47e1994989430a3ef5162 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 25 Sep 2019 15:59:36 +0800 Subject: [PATCH 024/118] Fix deploy script covered. --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6f61c24c4..037fa6689 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -56,7 +56,7 @@ for: - mingw32-make --version build_script: &python-script - cd cython && python setup.py test && cd .. - deploy_script: &python-deploy + after_build: &python-deploy # PyPI deployment - IF "%APPVEYOR_REPO_TAG%"=="true" IF "%APPVEYOR_REPO_NAME%"=="KmolYuan/solvespace" ( @@ -72,7 +72,7 @@ for: PYTHON_DIR: C:\Python37-x64 install: *python-install build_script: *python-script - deploy_script: *python-deploy + after_build: *python-deploy artifacts: - path: build\bin\%BUILD_TYPE%\solvespace.exe From 40d4c37bdafb3d2156de0bc26a847da063cba006 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 25 Sep 2019 16:09:09 +0800 Subject: [PATCH 025/118] Remove *.whl artifact. --- appveyor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 037fa6689..56b49d6bf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -81,4 +81,3 @@ artifacts: name: solvespace-cli.exe - path: build\bin\%BUILD_TYPE%\solvespace.pdb name: solvespace.pdb - - path: cython\dist\*.whl From b76206afc647dbc55f8839c1fb0a05078c34f0f4 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 25 Sep 2019 18:04:25 +0800 Subject: [PATCH 026/118] Update version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 550943c87..017462eac 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.0" +__version__ = "3.0.0.post0" from .slvs import ( quaternion_u, From a022604e1630d9ec590aa0c79af98b9526faf0d1 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 25 Sep 2019 18:32:27 +0800 Subject: [PATCH 027/118] Update more information. --- cython/README.md | 17 +++++++++-------- cython/python_solvespace/__init__.py | 2 +- cython/setup.py | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/cython/README.md b/cython/README.md index bd8b5fdaf..259013697 100644 --- a/cython/README.md +++ b/cython/README.md @@ -4,19 +4,20 @@ [![PyPI](https://img.shields.io/pypi/v/python-solvespace.svg)](https://pypi.org/project/python-solvespace/) [![GitHub license](https://img.shields.io/badge/license-GPLv3+-blue.svg)](https://raw.githubusercontent.com/KmolYuan/solvespace/master/LICENSE) -python-solvespace -=== +# python-solvespace Python library from solver of SolveSpace. -Feature for CDemo and Python interface can see [here](https://github.com/solvespace/solvespace/blob/master/exposed/DOC.txt). ++ [Python API](https://pyslvs-ui.readthedocs.io/en/stable/python-solvespace-api/) ++ [C API](https://github.com/solvespace/solvespace/blob/master/exposed/DOC.txt) -Build and Test -=== +# Install -Requirement: +```bash +pip install python-solvespace +``` -+ [Cython] +# Build and Test (Repository) Build and install the module: @@ -33,7 +34,7 @@ python tests Uninstall the module: ```bash -pip uninstall python_solvespace +pip uninstall python-solvespace ``` [GNU Make]: https://sourceforge.net/projects/mingw-w64/files/latest/download?source=files diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 017462eac..99b98f069 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.0.post0" +__version__ = "3.0.0.post1" from .slvs import ( quaternion_u, diff --git a/cython/setup.py b/cython/setup.py index 92b63e0fd..52a394bd4 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -114,7 +114,7 @@ def run(self): description="Python library of Solvespace", long_description=read("README.md"), long_description_content_type='text/markdown', - url="https://github.com/solvespace/solvespace", + url="https://github.com/KmolYuan/solvespace", packages=find_packages(exclude=('tests',)), package_data={'': ["*.pyi"]}, ext_modules=[Extension( From 4f034441c25b01cdef9dc9dd50628cb3157f5efd Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Thu, 26 Sep 2019 22:21:07 +0800 Subject: [PATCH 028/118] Fix requirements.txt missing in source. --- .gitignore | 1 + cython/MANIFEST.in | 2 ++ cython/python_solvespace/__init__.py | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 cython/MANIFEST.in diff --git a/.gitignore b/.gitignore index 5301e1426..39493ffab 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ /debian/libslvs1-dev/ /obj-*/ /*.slvs +.idea/* diff --git a/cython/MANIFEST.in b/cython/MANIFEST.in new file mode 100644 index 000000000..4fae91089 --- /dev/null +++ b/cython/MANIFEST.in @@ -0,0 +1,2 @@ +include requirements.txt +include README.md diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 99b98f069..62bb26a70 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.0.post1" +__version__ = "3.0.0.post2" from .slvs import ( quaternion_u, From 989008e3e9166252472b4db636b80d615af0524d Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 27 Sep 2019 09:19:43 +0800 Subject: [PATCH 029/118] Fix the distribution from sources. --- cython/.gitignore | 132 +++--------------- cython/MANIFEST.in | 1 + .../dist/python_solvespace-3.0.0.post2.tar.gz | Bin 0 -> 99488 bytes cython/platform/config.h | 3 + cython/python_solvespace.egg-info/PKG-INFO | 57 ++++++++ cython/python_solvespace.egg-info/SOURCES.txt | 53 +++++++ .../dependency_links.txt | 1 + .../python_solvespace.egg-info/requires.txt | 3 + .../python_solvespace.egg-info/top_level.txt | 1 + cython/python_solvespace/__init__.py | 2 +- cython/setup.py | 65 +++++---- 11 files changed, 174 insertions(+), 144 deletions(-) create mode 100644 cython/dist/python_solvespace-3.0.0.post2.tar.gz create mode 100644 cython/platform/config.h create mode 100644 cython/python_solvespace.egg-info/PKG-INFO create mode 100644 cython/python_solvespace.egg-info/SOURCES.txt create mode 100644 cython/python_solvespace.egg-info/dependency_links.txt create mode 100644 cython/python_solvespace.egg-info/requires.txt create mode 100644 cython/python_solvespace.egg-info/top_level.txt diff --git a/cython/.gitignore b/cython/.gitignore index 612cc2db7..b0b329d10 100644 --- a/cython/.gitignore +++ b/cython/.gitignore @@ -1,117 +1,17 @@ -# cython +/CMakeCache.txt +/build*/ +/test/**/*.diff.* +/test/**/*.curr.* +*.trace +/debian/tmp/ +/debian/*.log +/debian/*.substvars +/debian/*.debhelper +/debian/files +/debian/solvespace/ +/debian/libslvs1/ +/debian/libslvs1-dev/ +/obj-*/ +/*.slvs +.idea/* python_solvespace/*.cpp - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so -*.o* -*.cxx -*.def -*.lib - -# Distribution / packaging -.Python -env/ -build/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -*.egg-info/ -.installed.cfg -*.egg -AppImageAssistant - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*.cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: -*.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# Jupyter Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# celery beat schedule file -celerybeat-schedule - -# SageMath parsed files -*.sage.py - -# dotenv -.env - -# virtualenv -.venv -venv/ -ENV/ -out/ - -# Spyder project settings -.spyderproject -.spyproject - -# Rope project settings -.ropeproject - -# mkdocs documentation -/site - -# mypy -.mypy_cache/ - -#PyCharm cache -.idea/ - -# Others -.DS_Store -*/.DS_Store diff --git a/cython/MANIFEST.in b/cython/MANIFEST.in index 4fae91089..a3dcb24d1 100644 --- a/cython/MANIFEST.in +++ b/cython/MANIFEST.in @@ -1,2 +1,3 @@ include requirements.txt include README.md +recursive-include . *.h diff --git a/cython/dist/python_solvespace-3.0.0.post2.tar.gz b/cython/dist/python_solvespace-3.0.0.post2.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..b2de7205094f118d3a4960630ea24569422bc715 GIT binary patch literal 99488 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

JOJ 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.6 +Description-Content-Type: text/markdown diff --git a/cython/python_solvespace.egg-info/SOURCES.txt b/cython/python_solvespace.egg-info/SOURCES.txt new file mode 100644 index 000000000..5a61ea049 --- /dev/null +++ b/cython/python_solvespace.egg-info/SOURCES.txt @@ -0,0 +1,53 @@ +MANIFEST.in +README.md +requirements.txt +setup.py +./platform/config.h +./python_solvespace/include/slvs.h +./python_solvespace/src/dsc.h +./python_solvespace/src/expr.h +./python_solvespace/src/polygon.h +./python_solvespace/src/resource.h +./python_solvespace/src/sketch.h +./python_solvespace/src/solvespace.h +./python_solvespace/src/ttf.h +./python_solvespace/src/ui.h +./python_solvespace/src/platform/gui.h +./python_solvespace/src/platform/platform.h +./python_solvespace/src/render/gl3shader.h +./python_solvespace/src/render/render.h +./python_solvespace/src/srf/surface.h +platform/config.h +python_solvespace/__init__.pxd +python_solvespace/__init__.py +python_solvespace/slvs.pxd +python_solvespace/slvs.pyi +python_solvespace/slvs.pyx +python_solvespace.egg-info/PKG-INFO +python_solvespace.egg-info/SOURCES.txt +python_solvespace.egg-info/dependency_links.txt +python_solvespace.egg-info/requires.txt +python_solvespace.egg-info/top_level.txt +python_solvespace/include/slvs.h +python_solvespace/src/constraint.cpp +python_solvespace/src/constrainteq.cpp +python_solvespace/src/dsc.h +python_solvespace/src/entity.cpp +python_solvespace/src/expr.cpp +python_solvespace/src/expr.h +python_solvespace/src/lib.cpp +python_solvespace/src/polygon.h +python_solvespace/src/resource.h +python_solvespace/src/sketch.h +python_solvespace/src/solvespace.h +python_solvespace/src/system.cpp +python_solvespace/src/ttf.h +python_solvespace/src/ui.h +python_solvespace/src/util.cpp +python_solvespace/src/platform/gui.h +python_solvespace/src/platform/platform.cpp +python_solvespace/src/platform/platform.h +python_solvespace/src/platform/utilwin.cpp +python_solvespace/src/render/gl3shader.h +python_solvespace/src/render/render.h +python_solvespace/src/srf/surface.h \ No newline at end of file diff --git a/cython/python_solvespace.egg-info/dependency_links.txt b/cython/python_solvespace.egg-info/dependency_links.txt new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/cython/python_solvespace.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/cython/python_solvespace.egg-info/requires.txt b/cython/python_solvespace.egg-info/requires.txt new file mode 100644 index 000000000..d1e61c4a5 --- /dev/null +++ b/cython/python_solvespace.egg-info/requires.txt @@ -0,0 +1,3 @@ +setuptools +wheel +cython diff --git a/cython/python_solvespace.egg-info/top_level.txt b/cython/python_solvespace.egg-info/top_level.txt new file mode 100644 index 000000000..4ee51a6a4 --- /dev/null +++ b/cython/python_solvespace.egg-info/top_level.txt @@ -0,0 +1 @@ +python_solvespace diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 62bb26a70..c3d39c27c 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.0.post2" +__version__ = "3.0.0.post3" from .slvs import ( quaternion_u, diff --git a/cython/setup.py b/cython/setup.py index 52a394bd4..17d7fe5ac 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -7,24 +7,26 @@ __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -import os from os.path import ( abspath, dirname, + isdir, join as pth_join, ) import re import codecs -from textwrap import dedent from setuptools import setup, Extension, find_packages from setuptools.command.build_ext import build_ext +from setuptools.command.sdist import sdist +from distutils import dir_util from platform import system from distutils import sysconfig here = abspath(dirname(__file__)) -include_path = '../include/' -src_path = '../src/' -platform_path = src_path + 'platform/' +include_path = pth_join('python_solvespace', 'include') +src_path = pth_join('python_solvespace', 'src') +platform_path = pth_join(src_path, 'platform') +extra_path = pth_join(here, 'platform') ver = sysconfig.get_config_var('VERSION') lib = sysconfig.get_config_var('BINDIR') @@ -66,14 +68,14 @@ def find_version(*file_paths): ] 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', + pth_join('python_solvespace', 'slvs.pyx'), + pth_join(src_path, 'util.cpp'), + pth_join(src_path, 'entity.cpp'), + pth_join(src_path, 'expr.cpp'), + pth_join(src_path, 'constrainteq.cpp'), + pth_join(src_path, 'constraint.cpp'), + pth_join(src_path, 'system.cpp'), + pth_join(src_path, 'lib.cpp'), ] if system() == 'Windows': @@ -87,23 +89,32 @@ def find_version(*file_paths): macros.append(('WIN32', None)) # Platform sources - sources.append(platform_path + 'utilwin.cpp') - sources.append(platform_path + 'platform.cpp') + sources.append(pth_join(platform_path, 'utilwin.cpp')) + sources.append(pth_join(platform_path, 'platform.cpp')) else: - sources.append(platform_path + 'utilunix.cpp') + sources.append(pth_join(platform_path, 'utilunix.cpp')) class Build(build_ext): def run(self): - # Generate "config.h", actually not used. - config_h = src_path + "config.h" - write(dedent(f"""\ - #ifndef SOLVESPACE_CONFIG_H - #define SOLVESPACE_CONFIG_H - #endif - """), config_h) + has_src = isdir(include_path) and isdir(src_path) + if not has_src: + dir_util.copy_tree(pth_join('..', 'include'), include_path) + dir_util.copy_tree(pth_join('..', 'src'), src_path) super(Build, self).run() - os.remove(config_h) + if not has_src: + dir_util.remove_tree(include_path, dry_run=self.dry_run) + dir_util.remove_tree(src_path, dry_run=self.dry_run) + + +class PackSource(sdist): + def run(self): + dir_util.copy_tree(pth_join('..', 'include'), include_path) + dir_util.copy_tree(pth_join('..', 'src'), src_path) + super(PackSource, self).run() + if not self.keep_temp: + dir_util.remove_tree(include_path, dry_run=self.dry_run) + dir_util.remove_tree(src_path, dry_run=self.dry_run) setup( @@ -116,16 +127,16 @@ def run(self): long_description_content_type='text/markdown', url="https://github.com/KmolYuan/solvespace", packages=find_packages(exclude=('tests',)), - package_data={'': ["*.pyi"]}, + package_data={'': ["*.pyi", "*.pxd"]}, ext_modules=[Extension( "python_solvespace.slvs", sources, language="c++", - include_dirs=[include_path, src_path, platform_path], + include_dirs=[include_path, src_path, platform_path, extra_path], define_macros=macros, extra_compile_args=compile_args )], - cmdclass={'build_ext': Build}, + cmdclass={'build_ext': Build, 'sdist': PackSource}, python_requires=">=3.6", install_requires=read('requirements.txt').splitlines(), test_suite="tests", From 88d828546c413f2dcdc1f3cdd6dceaf3999ec27d Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 27 Sep 2019 14:18:22 +0800 Subject: [PATCH 030/118] Reduce the source files of Solvespace. --- cython/.gitignore | 116 +++++++++++++++++- .../dist/python_solvespace-3.0.0.post2.tar.gz | Bin 99488 -> 0 bytes cython/python_solvespace.egg-info/PKG-INFO | 57 --------- cython/python_solvespace.egg-info/SOURCES.txt | 53 -------- .../dependency_links.txt | 1 - .../python_solvespace.egg-info/requires.txt | 3 - .../python_solvespace.egg-info/top_level.txt | 1 - cython/python_solvespace/__init__.py | 2 +- cython/setup.py | 28 ++++- 9 files changed, 138 insertions(+), 123 deletions(-) delete mode 100644 cython/dist/python_solvespace-3.0.0.post2.tar.gz delete mode 100644 cython/python_solvespace.egg-info/PKG-INFO delete mode 100644 cython/python_solvespace.egg-info/SOURCES.txt delete mode 100644 cython/python_solvespace.egg-info/dependency_links.txt delete mode 100644 cython/python_solvespace.egg-info/requires.txt delete mode 100644 cython/python_solvespace.egg-info/top_level.txt diff --git a/cython/.gitignore b/cython/.gitignore index b0b329d10..71d712319 100644 --- a/cython/.gitignore +++ b/cython/.gitignore @@ -13,5 +13,119 @@ /debian/libslvs1-dev/ /obj-*/ /*.slvs -.idea/* python_solvespace/*.cpp + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so +*.o* +*.cxx +*.def +*.lib + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +AppImageAssistant + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ +out/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +#PyCharm cache +.idea/ + +# Others +.DS_Store +*/.DS_Store diff --git a/cython/dist/python_solvespace-3.0.0.post2.tar.gz b/cython/dist/python_solvespace-3.0.0.post2.tar.gz deleted file mode 100644 index b2de7205094f118d3a4960630ea24569422bc715..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 99488 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

JOJ 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.6 -Description-Content-Type: text/markdown diff --git a/cython/python_solvespace.egg-info/SOURCES.txt b/cython/python_solvespace.egg-info/SOURCES.txt deleted file mode 100644 index 5a61ea049..000000000 --- a/cython/python_solvespace.egg-info/SOURCES.txt +++ /dev/null @@ -1,53 +0,0 @@ -MANIFEST.in -README.md -requirements.txt -setup.py -./platform/config.h -./python_solvespace/include/slvs.h -./python_solvespace/src/dsc.h -./python_solvespace/src/expr.h -./python_solvespace/src/polygon.h -./python_solvespace/src/resource.h -./python_solvespace/src/sketch.h -./python_solvespace/src/solvespace.h -./python_solvespace/src/ttf.h -./python_solvespace/src/ui.h -./python_solvespace/src/platform/gui.h -./python_solvespace/src/platform/platform.h -./python_solvespace/src/render/gl3shader.h -./python_solvespace/src/render/render.h -./python_solvespace/src/srf/surface.h -platform/config.h -python_solvespace/__init__.pxd -python_solvespace/__init__.py -python_solvespace/slvs.pxd -python_solvespace/slvs.pyi -python_solvespace/slvs.pyx -python_solvespace.egg-info/PKG-INFO -python_solvespace.egg-info/SOURCES.txt -python_solvespace.egg-info/dependency_links.txt -python_solvespace.egg-info/requires.txt -python_solvespace.egg-info/top_level.txt -python_solvespace/include/slvs.h -python_solvespace/src/constraint.cpp -python_solvespace/src/constrainteq.cpp -python_solvespace/src/dsc.h -python_solvespace/src/entity.cpp -python_solvespace/src/expr.cpp -python_solvespace/src/expr.h -python_solvespace/src/lib.cpp -python_solvespace/src/polygon.h -python_solvespace/src/resource.h -python_solvespace/src/sketch.h -python_solvespace/src/solvespace.h -python_solvespace/src/system.cpp -python_solvespace/src/ttf.h -python_solvespace/src/ui.h -python_solvespace/src/util.cpp -python_solvespace/src/platform/gui.h -python_solvespace/src/platform/platform.cpp -python_solvespace/src/platform/platform.h -python_solvespace/src/platform/utilwin.cpp -python_solvespace/src/render/gl3shader.h -python_solvespace/src/render/render.h -python_solvespace/src/srf/surface.h \ No newline at end of file diff --git a/cython/python_solvespace.egg-info/dependency_links.txt b/cython/python_solvespace.egg-info/dependency_links.txt deleted file mode 100644 index 8b1378917..000000000 --- a/cython/python_solvespace.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/cython/python_solvespace.egg-info/requires.txt b/cython/python_solvespace.egg-info/requires.txt deleted file mode 100644 index d1e61c4a5..000000000 --- a/cython/python_solvespace.egg-info/requires.txt +++ /dev/null @@ -1,3 +0,0 @@ -setuptools -wheel -cython diff --git a/cython/python_solvespace.egg-info/top_level.txt b/cython/python_solvespace.egg-info/top_level.txt deleted file mode 100644 index 4ee51a6a4..000000000 --- a/cython/python_solvespace.egg-info/top_level.txt +++ /dev/null @@ -1 +0,0 @@ -python_solvespace diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index c3d39c27c..520eb1135 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.0.post3" +__version__ = "3.0.0.post4" from .slvs import ( quaternion_u, diff --git a/cython/setup.py b/cython/setup.py index 17d7fe5ac..ec44f0441 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -7,6 +7,7 @@ __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" +from os import walk from os.path import ( abspath, dirname, @@ -18,7 +19,7 @@ from setuptools import setup, Extension, find_packages from setuptools.command.build_ext import build_ext from setuptools.command.sdist import sdist -from distutils import dir_util +from distutils import file_util, dir_util from platform import system from distutils import sysconfig @@ -26,7 +27,7 @@ include_path = pth_join('python_solvespace', 'include') src_path = pth_join('python_solvespace', 'src') platform_path = pth_join(src_path, 'platform') -extra_path = pth_join(here, 'platform') +extra_path = 'platform' ver = sysconfig.get_config_var('VERSION') lib = sysconfig.get_config_var('BINDIR') @@ -95,12 +96,27 @@ def find_version(*file_paths): sources.append(pth_join(platform_path, 'utilunix.cpp')) +def copy_source(dry_run): + dir_util.copy_tree(pth_join('..', 'include'), include_path, dry_run=dry_run) + dir_util.mkpath(pth_join('python_solvespace', 'src')) + for root, _, files in walk(pth_join('..', 'src')): + for f in files: + if not f.endswith('.h'): + continue + f = pth_join(root, f) + f_new = f.replace('..', 'python_solvespace') + if not isdir(dirname(f_new)): + dir_util.mkpath(dirname(f_new)) + file_util.copy_file(f, f_new, dry_run=dry_run) + for f in sources[1:]: + file_util.copy_file(f.replace('python_solvespace', '..'), f, dry_run=dry_run) + + class Build(build_ext): def run(self): has_src = isdir(include_path) and isdir(src_path) if not has_src: - dir_util.copy_tree(pth_join('..', 'include'), include_path) - dir_util.copy_tree(pth_join('..', 'src'), src_path) + copy_source(self.dry_run) super(Build, self).run() if not has_src: dir_util.remove_tree(include_path, dry_run=self.dry_run) @@ -109,8 +125,7 @@ def run(self): class PackSource(sdist): def run(self): - dir_util.copy_tree(pth_join('..', 'include'), include_path) - dir_util.copy_tree(pth_join('..', 'src'), src_path) + copy_source(self.dry_run) super(PackSource, self).run() if not self.keep_temp: dir_util.remove_tree(include_path, dry_run=self.dry_run) @@ -137,6 +152,7 @@ def run(self): extra_compile_args=compile_args )], cmdclass={'build_ext': Build, 'sdist': PackSource}, + zip_safe=False, python_requires=">=3.6", install_requires=read('requirements.txt').splitlines(), test_suite="tests", From b49366b82022b28a270ad714cd0ace83cf89a620 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 27 Sep 2019 21:21:21 +0800 Subject: [PATCH 031/118] Format setup script. --- cython/setup.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/cython/setup.py b/cython/setup.py index ec44f0441..9f73c3e39 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -57,7 +57,6 @@ def find_version(*file_paths): ('EXPORT_DLL', None), ('_CRT_SECURE_NO_WARNINGS', None), ] - compile_args = [ '-O3', '-Wno-cpp', @@ -67,7 +66,6 @@ def find_version(*file_paths): '-fPIC', '-std=c++11', ] - sources = [ pth_join('python_solvespace', 'slvs.pyx'), pth_join(src_path, 'util.cpp'), @@ -78,17 +76,14 @@ def find_version(*file_paths): pth_join(src_path, 'system.cpp'), pth_join(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(pth_join(platform_path, 'utilwin.cpp')) sources.append(pth_join(platform_path, 'platform.cpp')) @@ -137,7 +132,7 @@ def run(self): version=find_version('python_solvespace', '__init__.py'), author=__author__, author_email=__email__, - description="Python library of Solvespace", + description="Python library of Solvespace.", long_description=read("README.md"), long_description_content_type='text/markdown', url="https://github.com/KmolYuan/solvespace", From 8cfb613344de5b393022eab1ad9cd711c4bb5088 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 29 Sep 2019 19:37:14 +0800 Subject: [PATCH 032/118] Update classifiers of supported Python version. --- cython/python_solvespace/__init__.py | 2 +- cython/setup.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 520eb1135..1e4a1b42f 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.0.post4" +__version__ = "3.0.0.post5" from .slvs import ( quaternion_u, diff --git a/cython/setup.py b/cython/setup.py index 9f73c3e39..1de21adb8 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -152,7 +152,9 @@ def run(self): install_requires=read('requirements.txt').splitlines(), test_suite="tests", classifiers=[ - "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", "Programming Language :: Cython", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Operating System :: OS Independent", From 18c05748fc9abbf47dde04687c54d928206fe1f2 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 6 Oct 2019 17:49:53 +0800 Subject: [PATCH 033/118] Support MSVC compiler. --- cython/setup.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/cython/setup.py b/cython/setup.py index 1de21adb8..e771a948d 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -108,6 +108,17 @@ def copy_source(dry_run): class Build(build_ext): + def build_extensions(self): + if self.compiler.compiler_type in {'mingw32'}: + for e in self.extensions: + e.define_macros = macros + e.extra_compile_args = compile_args + elif self.compiler.compiler_type == 'msvc': + for e in self.extensions: + e.define_macros = [('_USE_MATH_DEFINES', None)] + macros[2:] + e.libraries = ['shell32'] + super(Build, self).build_extensions() + def run(self): has_src = isdir(include_path) and isdir(src_path) if not has_src: @@ -142,9 +153,7 @@ def run(self): "python_solvespace.slvs", sources, language="c++", - include_dirs=[include_path, src_path, platform_path, extra_path], - define_macros=macros, - extra_compile_args=compile_args + include_dirs=[include_path, src_path, platform_path, extra_path] )], cmdclass={'build_ext': Build, 'sdist': PackSource}, zip_safe=False, From 6497133e1a14500b536195d227448010ea983078 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 6 Oct 2019 18:08:46 +0800 Subject: [PATCH 034/118] Apply build config for unix. --- cython/setup.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cython/setup.py b/cython/setup.py index e771a948d..cdec73d90 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -23,7 +23,6 @@ from platform import system from distutils import sysconfig -here = abspath(dirname(__file__)) include_path = pth_join('python_solvespace', 'include') src_path = pth_join('python_solvespace', 'src') platform_path = pth_join(src_path, 'platform') @@ -33,12 +32,12 @@ def write(doc, *parts): - with codecs.open(pth_join(here, *parts), 'w') as f: + with codecs.open(pth_join(*parts), 'w') as f: f.write(doc) def read(*parts): - with codecs.open(pth_join(here, *parts), 'r') as f: + with codecs.open(pth_join(*parts), 'r') as f: return f.read() @@ -109,11 +108,12 @@ def copy_source(dry_run): class Build(build_ext): def build_extensions(self): - if self.compiler.compiler_type in {'mingw32'}: + compiler = self.compiler.compiler_type + if compiler in {'mingw32', 'unix'}: for e in self.extensions: e.define_macros = macros e.extra_compile_args = compile_args - elif self.compiler.compiler_type == 'msvc': + elif compiler == 'msvc': for e in self.extensions: e.define_macros = [('_USE_MATH_DEFINES', None)] + macros[2:] e.libraries = ['shell32'] From de609637d16337532d054e352984d0efb2a52bca Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 6 Oct 2019 18:12:06 +0800 Subject: [PATCH 035/118] Update version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 1e4a1b42f..54bc8802f 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.0.post5" +__version__ = "3.0.0.post6" from .slvs import ( quaternion_u, From bd616e898d61fb9aa70eef1d2e239d08655df5a4 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 8 Oct 2019 21:25:03 +0800 Subject: [PATCH 036/118] Add MSVC CI builder. --- appveyor.yml | 27 +++++++++++---------------- cython/platform/set_pycompiler.bat | 6 ++++-- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 56b49d6bf..338de3303 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,8 +6,13 @@ environment: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 PYTHON_DIR: C:\Python36-x64 + COMPILER: mingw32 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 PYTHON_DIR: C:\Python37-x64 + COMPILER: mingw32 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DIR: C:\Python37-x64 + COMPILER: msvc for: - matrix: only: @@ -33,12 +38,10 @@ for: on: APPVEYOR_REPO_NAME: solvespace/solvespace APPVEYOR_REPO_TAG: true - - matrix: only: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON_DIR: C:\Python36-x64 - install: &python-install + install: # Environment variables - set Path=%MSYS_DIR%\mingw64\bin;%MSYS_DIR%\usr\bin;%Path% - set Path=%PYTHON_DIR%;%PYTHON_DIR%\Scripts;%Path% @@ -48,32 +51,24 @@ for: # Upgrade setuptools - pip install setuptools -U # Set Python compiler to MinGW - - cython\platform\set_pycompiler %PYTHON_DIR% + - cython\platform\set_pycompiler %PYTHON_DIR% %COMPILER% # Install modules - pip install -r cython\requirements.txt # Show tool kits - gcc --version - mingw32-make --version - build_script: &python-script + build_script: - cd cython && python setup.py test && cd .. - after_build: &python-deploy + after_build: # PyPI deployment - IF "%APPVEYOR_REPO_TAG%"=="true" - IF "%APPVEYOR_REPO_NAME%"=="KmolYuan/solvespace" ( + IF "%APPVEYOR_REPO_NAME%"=="KmolYuan/solvespace" + IF "%COMPILER%"=="mingw32" ( pip install twine && cd cython && python setup.py bdist_wheel && twine upload dist\*.whl --skip-existing ) - - - matrix: - only: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON_DIR: C:\Python37-x64 - install: *python-install - build_script: *python-script - after_build: *python-deploy - artifacts: - path: build\bin\%BUILD_TYPE%\solvespace.exe name: solvespace.exe diff --git a/cython/platform/set_pycompiler.bat b/cython/platform/set_pycompiler.bat index 03ec1ff12..5d32358e4 100644 --- a/cython/platform/set_pycompiler.bat +++ b/cython/platform/set_pycompiler.bat @@ -1,14 +1,16 @@ echo off -REM Usage: set_pycompiler C:\Python37 +REM Usage: set_pycompiler C:\Python37 mingw32 REM Where %PYTHON_DIR% is the directory of your Python installation. +REM Compiler option can be "mingw32" or "msvc". REM In Pyslvs project. set HERE=%~dp0 set PYTHON_DIR=%1 +set COMPILER=%2 REM Create "distutils.cfg" echo [build]>> "%PYTHON_DIR%\Lib\distutils\distutils.cfg" -echo compiler=mingw32>> "%PYTHON_DIR%\Lib\distutils\distutils.cfg" +echo compiler=%COMPILER%>> "%PYTHON_DIR%\Lib\distutils\distutils.cfg" REM Apply the patch of "cygwinccompiler.py". REM Unix "patch" command of Msys. From 8952591dbd9529b5feb2bd458b8f8aa6b52440dd Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 8 Oct 2019 22:06:04 +0800 Subject: [PATCH 037/118] Allow reusing "set_compiler" command. --- cython/platform/set_pycompiler.bat | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cython/platform/set_pycompiler.bat b/cython/platform/set_pycompiler.bat index 5d32358e4..02d45e468 100644 --- a/cython/platform/set_pycompiler.bat +++ b/cython/platform/set_pycompiler.bat @@ -9,12 +9,15 @@ set PYTHON_DIR=%1 set COMPILER=%2 REM Create "distutils.cfg" -echo [build]>> "%PYTHON_DIR%\Lib\distutils\distutils.cfg" -echo compiler=%COMPILER%>> "%PYTHON_DIR%\Lib\distutils\distutils.cfg" - +set DISTUTILS=%PYTHON_DIR%\Lib\distutils\distutils.cfg +if exist "%DISTUTILS%" del "%DISTUTILS%" /Q /S +echo [build]>> "%DISTUTILS%" +echo compiler=%COMPILER%>> "%DISTUTILS%" +echo patched file "%DISTUTILS%" REM Apply the patch of "cygwinccompiler.py". REM Unix "patch" command of Msys. -patch "%PYTHON_DIR%\lib\distutils\cygwinccompiler.py" "%HERE%patch.diff" +patch -N "%PYTHON_DIR%\lib\distutils\cygwinccompiler.py" "%HERE%\patch.diff" REM Copy "vcruntime140.dll" to "libs". copy "%PYTHON_DIR%\vcruntime140.dll" "%PYTHON_DIR%\libs" +echo copied "vcruntime140.dll". From 2da3e6bf8058badd2be370dcf3e5955327d35ae0 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Thu, 10 Oct 2019 15:44:59 +0800 Subject: [PATCH 038/118] Change type hint of magic method. --- cython/python_solvespace/slvs.pyx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index bbd8a942c..fa4cc035e 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -9,7 +9,6 @@ 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 @@ -150,7 +149,7 @@ cdef class Entity: entity.params = Params.create(e.param, p_size) return entity - def __richcmp__(self, other: Entity, op: cython.int) -> bint: + def __richcmp__(self, Entity other, int op) -> bint: """Compare the entities.""" if op == Py_EQ: return ( From 675aa76d5ca55874367784c5c45fb14987c50a4b Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 12 Oct 2019 12:26:29 +0800 Subject: [PATCH 039/118] Support PEP 561. --- cython/python_solvespace/py.typed | 0 cython/setup.py | 11 +++-------- 2 files changed, 3 insertions(+), 8 deletions(-) create mode 100644 cython/python_solvespace/py.typed diff --git a/cython/python_solvespace/py.typed b/cython/python_solvespace/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/cython/setup.py b/cython/setup.py index cdec73d90..cfd993740 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -8,12 +8,7 @@ __email__ = "pyslvs@gmail.com" from os import walk -from os.path import ( - abspath, - dirname, - isdir, - join as pth_join, -) +from os.path import dirname, isdir, join as pth_join import re import codecs from setuptools import setup, Extension, find_packages @@ -148,7 +143,7 @@ def run(self): long_description_content_type='text/markdown', url="https://github.com/KmolYuan/solvespace", packages=find_packages(exclude=('tests',)), - package_data={'': ["*.pyi", "*.pxd"]}, + package_data={'': ["*.pyi", "*.pxd"], 'python_solvespace': ['py.typed']}, ext_modules=[Extension( "python_solvespace.slvs", sources, @@ -159,7 +154,7 @@ def run(self): zip_safe=False, python_requires=">=3.6", install_requires=read('requirements.txt').splitlines(), - test_suite="tests", + test_suite='tests', classifiers=[ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", From 4ac257e3f52f4f2b8e74db66120a968037921547 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 12 Oct 2019 12:28:47 +0800 Subject: [PATCH 040/118] Update version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 54bc8802f..c48128bba 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.0.post6" +__version__ = "3.0.0.post7" from .slvs import ( quaternion_u, From 6e4de28045f928520d2516083e70f1e23a130263 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 15 Oct 2019 14:43:17 +0800 Subject: [PATCH 041/118] Update git ignore. --- cython/.gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cython/.gitignore b/cython/.gitignore index 71d712319..e254ee788 100644 --- a/cython/.gitignore +++ b/cython/.gitignore @@ -14,6 +14,8 @@ /obj-*/ /*.slvs python_solvespace/*.cpp +python_solvespace/src/ +python_solvespace/include/ # Byte-compiled / optimized / DLL files __pycache__/ From 9d1b9e7e432ada819628fdc84a877e35b4515a8f Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 15 Oct 2019 16:13:16 +0800 Subject: [PATCH 042/118] Move enums to Python script to reduce library size. --- cython/python_solvespace/__init__.py | 51 ++++++- cython/python_solvespace/slvs.pxd | 89 +++++------- cython/python_solvespace/slvs.pyi | 52 +------ cython/python_solvespace/slvs.pyx | 209 ++++++++++++++------------- 4 files changed, 196 insertions(+), 205 deletions(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index c48128bba..0ef77246a 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,15 +8,14 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.0.post7" +__version__ = "3.0.1.dev0" +from enum import IntEnum, auto from .slvs import ( quaternion_u, quaternion_v, quaternion_n, make_quaternion, - Constraint, - ResultFlag, Params, Entity, SolverSystem, @@ -33,3 +32,49 @@ 'Entity', 'SolverSystem', ] + + +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() diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index 92f01762d..7b1f0eab8 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -31,12 +31,9 @@ cdef extern from "slvs.h" nogil: # 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 @@ -53,6 +50,41 @@ cdef extern from "slvs.h" nogil: Slvs_hEntity distance Slvs_hParam param[4] + int SLVS_C_POINTS_COINCIDENT + int SLVS_C_PT_PT_DISTANCE + int SLVS_C_PT_PLANE_DISTANCE + int SLVS_C_PT_LINE_DISTANCE + int SLVS_C_PT_FACE_DISTANCE + int SLVS_C_PT_IN_PLANE + int SLVS_C_PT_ON_LINE + int SLVS_C_PT_ON_FACE + int SLVS_C_EQUAL_LENGTH_LINES + int SLVS_C_LENGTH_RATIO + int SLVS_C_EQ_LEN_PT_LINE_D + int SLVS_C_EQ_PT_LN_DISTANCES + int SLVS_C_EQUAL_ANGLE + int SLVS_C_EQUAL_LINE_ARC_LEN + int SLVS_C_SYMMETRIC + int SLVS_C_SYMMETRIC_HORIZ + int SLVS_C_SYMMETRIC_VERT + int SLVS_C_SYMMETRIC_LINE + int SLVS_C_AT_MIDPOINT + int SLVS_C_HORIZONTAL + int SLVS_C_VERTICAL + int SLVS_C_DIAMETER + int SLVS_C_PT_ON_CIRCLE + int SLVS_C_SAME_ORIENTATION + int SLVS_C_ANGLE + int SLVS_C_PARALLEL + int SLVS_C_PERPENDICULAR + int SLVS_C_ARC_LINE_TANGENT + int SLVS_C_CUBIC_LINE_TANGENT + int SLVS_C_EQUAL_RADIUS + int SLVS_C_PROJ_PT_DISTANCE + int SLVS_C_WHERE_DRAGGED + int SLVS_C_CURVE_CURVE_TANGENT + int SLVS_C_LENGTH_DIFFERENCE + ctypedef struct Slvs_Constraint: Slvs_hConstraint h Slvs_hGroup group @@ -163,59 +195,11 @@ cdef extern from "slvs.h" nogil: 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 @@ -223,7 +207,6 @@ cdef class Params: @staticmethod cdef Params create(Slvs_hParam *p, size_t count) - cdef class Entity: cdef int t @@ -291,7 +274,7 @@ cdef class SolverSystem: cpdef Entity add_work_plane(self, Entity origin, Entity nm) cpdef void add_constraint( self, - Constraint c_type, + int c_type, Entity wp, double v, Entity p1, diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index ef6208bd7..a00ea7c5a 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -1,8 +1,6 @@ # -*- coding: utf-8 -*- from typing import Tuple, List, Sequence, Counter, ClassVar -from enum import IntEnum, auto - def quaternion_u( qw: float, @@ -39,52 +37,6 @@ def make_quaternion( ... -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: @@ -178,7 +130,7 @@ class SolverSystem: def faileds(self) -> List[int]: ... - def solve(self) -> ResultFlag: + def solve(self) -> int: ... def create_2d_base(self) -> Entity: @@ -219,7 +171,7 @@ class SolverSystem: def add_constraint( self, - c_type: Constraint, + c_type: int, wp: Entity, v: float, p1: Entity, diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index fa4cc035e..8eb7357d5 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -52,8 +52,20 @@ cdef class Params: 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: - cdef str m = f"{type(self).__name__}([" + m = f"{type(self).__name__}([" cdef size_t i cdef size_t s = self.param_list.size() for i in range(s): @@ -78,7 +90,7 @@ _E_NONE.g = 0 _E_NONE.params = Params.create(NULL, 0) # Entity names -cdef dict _NAME_OF_ENTITIES = { +_NAME_OF_ENTITIES = { SLVS_E_POINT_IN_3D: "point 3d", SLVS_E_POINT_IN_2D: "point 2d", SLVS_E_NORMAL_IN_2D: "normal 2d", @@ -92,41 +104,41 @@ cdef dict _NAME_OF_ENTITIES = { } # 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", +_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", } @@ -160,13 +172,7 @@ cdef class Entity: 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 - ) + return not (self == other) else: raise TypeError( f"'{op}' not support between instances of " @@ -323,7 +329,7 @@ cdef class SolverSystem: cpdef tuple params(self, Params p): """Get the parameters by Params object.""" - cdef list param_list = [] + param_list = [] cdef Slvs_hParam h for h in p.param_list: param_list.append(self.param_list[h].val) @@ -416,10 +422,8 @@ cdef class SolverSystem: """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): @@ -428,7 +432,8 @@ cdef class SolverSystem: 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)) + 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): @@ -437,7 +442,8 @@ cdef class SolverSystem: 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)) + 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): @@ -449,7 +455,8 @@ cdef class SolverSystem: 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)) + 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): @@ -459,7 +466,8 @@ cdef class SolverSystem: 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)) + 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): @@ -475,7 +483,8 @@ cdef class SolverSystem: 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)) + 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): @@ -490,8 +499,8 @@ cdef class SolverSystem: 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)) + 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): @@ -505,7 +514,8 @@ cdef class SolverSystem: 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)) + 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): @@ -520,7 +530,7 @@ cdef class SolverSystem: cpdef void add_constraint( self, - Constraint c_type, + int c_type, Entity wp, double v, Entity p1, @@ -567,16 +577,18 @@ cdef class SolverSystem: cpdef void coincident(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D): """Coincident two entities.""" - cdef Constraint t + cdef int t if e1.is_point() and e2.is_point(): - self.add_constraint(POINTS_COINCIDENT, wp, 0., e1, e2, _E_NONE, _E_NONE) + 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(PT_IN_PLANE, e2, 0., e1, _E_NONE, e2, _E_NONE) + 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 = PT_ON_LINE + t = SLVS_C_PT_ON_LINE else: - t = PT_ON_CIRCLE + 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}") @@ -592,24 +604,28 @@ cdef class SolverSystem: 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) + 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(PT_PLANE_DISTANCE, e2, value, e1, _E_NONE, e2, _E_NONE) + 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(PT_LINE_DISTANCE, wp, value, e1, _E_NONE, e2, _E_NONE) + 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.""" if e1.is_line() and e2.is_line(): - self.add_constraint(EQUAL_LENGTH_LINES, wp, 0., _E_NONE, _E_NONE, e1, e2) + 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(EQUAL_LINE_ARC_LEN, wp, 0., _E_NONE, _E_NONE, e1, e2) + 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(EQUAL_RADIUS, wp, 0., _E_NONE, _E_NONE, e1, e2) + self.add_constraint(SLVS_C_EQUAL_RADIUS, wp, 0., _E_NONE, _E_NONE, e1, e2) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") @@ -626,9 +642,9 @@ cdef class SolverSystem: """ 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) + 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}") @@ -645,9 +661,8 @@ cdef class SolverSystem: """ 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) + 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}") @@ -655,9 +670,9 @@ cdef class SolverSystem: """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) + 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}") @@ -670,13 +685,13 @@ cdef class SolverSystem: ): """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) + 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(SYMMETRIC, e3, 0., e1, e2, e3, _E_NONE) + 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(SYMMETRIC_LINE, wp, 0., e1, e2, e3, _E_NONE) + self.add_constraint(SLVS_C_SYMMETRIC_LINE, wp, 0., e1, e2, e3, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {e3}, {wp}") @@ -684,9 +699,8 @@ cdef class SolverSystem: """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) + self.add_constraint(SLVS_C_SYMMETRIC_HORIZ, wp, 0., e1, e2, _E_NONE, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") @@ -694,9 +708,8 @@ cdef class SolverSystem: """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) + self.add_constraint(SLVS_C_SYMMETRIC_VERT, wp, 0., e1, e2, _E_NONE, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") @@ -708,7 +721,7 @@ cdef class SolverSystem: ): """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) + self.add_constraint(SLVS_C_AT_MIDPOINT, wp, 0., e1, _E_NONE, e2, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") @@ -716,9 +729,8 @@ cdef class SolverSystem: """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) + self.add_constraint(SLVS_C_HORIZONTAL, wp, 0., _E_NONE, _E_NONE, e1, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {wp}") @@ -726,9 +738,8 @@ cdef class SolverSystem: """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) + self.add_constraint(SLVS_C_VERTICAL, wp, 0., _E_NONE, _E_NONE, e1, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {wp}") @@ -736,16 +747,17 @@ cdef class SolverSystem: """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) + 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.""" 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) + 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}") @@ -753,9 +765,8 @@ cdef class SolverSystem: """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, + 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}") @@ -764,9 +775,8 @@ cdef class SolverSystem: """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, + 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}") @@ -774,7 +784,7 @@ cdef class SolverSystem: 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) + self.add_constraint(SLVS_C_PARALLEL, wp, 0., _E_NONE, _E_NONE, e1, e2) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") @@ -783,26 +793,27 @@ cdef class SolverSystem: 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) + 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(CUBIC_LINE_TANGENT, wp, 0., _E_NONE, _E_NONE, e1, e2) + 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(CURVE_CURVE_TANGENT, wp, 0., _E_NONE, _E_NONE, e1, e2) + 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 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) + 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.""" if e1.is_point(): - self.add_constraint(WHERE_DRAGGED, wp, 0., e1, _E_NONE, _E_NONE, _E_NONE) + self.add_constraint(SLVS_C_WHERE_DRAGGED, wp, 0., e1, _E_NONE, _E_NONE, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {wp}") From c4928f7c94119c58aec57d9b5f3ab6e838a965ed Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 21 Oct 2019 23:13:17 +0800 Subject: [PATCH 043/118] Update version. --- cython/python_solvespace/__init__.py | 2 +- cython/setup.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 0ef77246a..39bfc9cfe 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.1.dev0" +__version__ = "3.0.1.post0" from enum import IntEnum, auto from .slvs import ( diff --git a/cython/setup.py b/cython/setup.py index cfd993740..e6667975a 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -7,6 +7,7 @@ __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" +import sys from os import walk from os.path import dirname, isdir, join as pth_join import re @@ -70,7 +71,10 @@ def find_version(*file_paths): pth_join(src_path, 'system.cpp'), pth_join(src_path, 'lib.cpp'), ] -if system() == 'Windows': +if {'sdist', 'bdist'} & set(sys.argv): + for s in ('utilwin', 'utilunix', 'platform'): + sources.append(pth_join(platform_path, f'{s}.cpp')) +elif system() == 'Windows': # Avoid compile error with CYTHON_USE_PYLONG_INTERNALS. # https://github.com/cython/cython/issues/2670#issuecomment-432212671 macros.append(('MS_WIN64', None)) From 1771ae45ca445505a511eadbc6ed65ffaf1ee3d9 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 23 Oct 2019 21:14:33 +0800 Subject: [PATCH 044/118] Support C++17. --- cython/setup.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cython/setup.py b/cython/setup.py index e6667975a..4464a272f 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -46,7 +46,8 @@ def find_version(*file_paths): macros = [ ('_hypot', 'hypot'), - ('M_PI', 'PI'), # C++ 11 + ('M_PI', 'PI'), + ('_USE_MATH_DEFINES', None), ('ISOLATION_AWARE_ENABLED', None), ('LIBRARY', None), ('EXPORT_DLL', None), @@ -59,7 +60,7 @@ def find_version(*file_paths): '-Wno-write-strings', '-fpermissive', '-fPIC', - '-std=c++11', + '-std=c++17', ] sources = [ pth_join('python_solvespace', 'slvs.pyx'), @@ -114,7 +115,7 @@ def build_extensions(self): e.extra_compile_args = compile_args elif compiler == 'msvc': for e in self.extensions: - e.define_macros = [('_USE_MATH_DEFINES', None)] + macros[2:] + e.define_macros = macros[2:] e.libraries = ['shell32'] super(Build, self).build_extensions() From 74bc01193ae2818c721f9883d64263ffc8bcb14e Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 26 Oct 2019 11:33:02 +0800 Subject: [PATCH 045/118] Update compiler options and patches. --- cython/platform/config.h | 3 --- .../platform/{patch.diff => cygwinccompiler.diff} | 0 cython/platform/pyconfig.diff | 15 +++++++++++++++ cython/platform/set_pycompiler.bat | 3 ++- cython/setup.py | 9 +++------ 5 files changed, 20 insertions(+), 10 deletions(-) delete mode 100644 cython/platform/config.h rename cython/platform/{patch.diff => cygwinccompiler.diff} (100%) create mode 100644 cython/platform/pyconfig.diff diff --git a/cython/platform/config.h b/cython/platform/config.h deleted file mode 100644 index 68720cdc9..000000000 --- a/cython/platform/config.h +++ /dev/null @@ -1,3 +0,0 @@ -#ifndef SOLVESPACE_CONFIG_H -#define SOLVESPACE_CONFIG_H -#endif diff --git a/cython/platform/patch.diff b/cython/platform/cygwinccompiler.diff similarity index 100% rename from cython/platform/patch.diff rename to cython/platform/cygwinccompiler.diff diff --git a/cython/platform/pyconfig.diff b/cython/platform/pyconfig.diff new file mode 100644 index 000000000..0a31106cc --- /dev/null +++ b/cython/platform/pyconfig.diff @@ -0,0 +1,15 @@ +--- pyconfig.h 2016-01-27 10:54:56.000000000 +0300 ++++ pyconfig.h 2016-03-28 11:46:48.000000000 +0300 +@@ -100,6 +100,12 @@ + + /* Compiler specific defines */ + ++#ifdef __MINGW32__ ++#ifdef _WIN64 ++#define MS_WIN64 ++#endif ++#endif ++ + /* ------------------------------------------------------------------------*/ + /* Microsoft C defines _MSC_VER */ + #ifdef _MSC_VER diff --git a/cython/platform/set_pycompiler.bat b/cython/platform/set_pycompiler.bat index 02d45e468..3e42cf8a0 100644 --- a/cython/platform/set_pycompiler.bat +++ b/cython/platform/set_pycompiler.bat @@ -16,7 +16,8 @@ echo compiler=%COMPILER%>> "%DISTUTILS%" echo patched file "%DISTUTILS%" REM Apply the patch of "cygwinccompiler.py". REM Unix "patch" command of Msys. -patch -N "%PYTHON_DIR%\lib\distutils\cygwinccompiler.py" "%HERE%\patch.diff" +patch -N "%PYTHON_DIR%\lib\distutils\cygwinccompiler.py" "%HERE%\cygwinccompiler.diff" +patch -N "%PYTHON_DIR%\include\pyconfig.h" "%HERE%\pyconfig.diff" REM Copy "vcruntime140.dll" to "libs". copy "%PYTHON_DIR%\vcruntime140.dll" "%PYTHON_DIR%\libs" diff --git a/cython/setup.py b/cython/setup.py index 4464a272f..db57e6070 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -22,7 +22,6 @@ include_path = pth_join('python_solvespace', 'include') src_path = pth_join('python_solvespace', 'src') platform_path = pth_join(src_path, 'platform') -extra_path = 'platform' ver = sysconfig.get_config_var('VERSION') lib = sysconfig.get_config_var('BINDIR') @@ -76,9 +75,6 @@ def find_version(*file_paths): for s in ('utilwin', 'utilunix', 'platform'): sources.append(pth_join(platform_path, f'{s}.cpp')) elif 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 @@ -92,7 +88,7 @@ def find_version(*file_paths): def copy_source(dry_run): dir_util.copy_tree(pth_join('..', 'include'), include_path, dry_run=dry_run) - dir_util.mkpath(pth_join('python_solvespace', 'src')) + dir_util.mkpath(src_path) for root, _, files in walk(pth_join('..', 'src')): for f in files: if not f.endswith('.h'): @@ -104,6 +100,7 @@ def copy_source(dry_run): file_util.copy_file(f, f_new, dry_run=dry_run) for f in sources[1:]: file_util.copy_file(f.replace('python_solvespace', '..'), f, dry_run=dry_run) + open(pth_join(platform_path, 'config.h'), 'a').close() class Build(build_ext): @@ -153,7 +150,7 @@ def run(self): "python_solvespace.slvs", sources, language="c++", - include_dirs=[include_path, src_path, platform_path, extra_path] + include_dirs=[include_path, src_path, platform_path] )], cmdclass={'build_ext': Build, 'sdist': PackSource}, zip_safe=False, From 700f0106e09f3e4061d6797ea676e2194e19ceb8 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 26 Oct 2019 21:40:33 +0800 Subject: [PATCH 046/118] Remove unused macro. --- cython/setup.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/cython/setup.py b/cython/setup.py index db57e6070..eb70b19e1 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -17,13 +17,10 @@ from setuptools.command.sdist import sdist from distutils import file_util, dir_util from platform import system -from distutils import sysconfig include_path = pth_join('python_solvespace', 'include') src_path = pth_join('python_solvespace', 'src') platform_path = pth_join(src_path, 'platform') -ver = sysconfig.get_config_var('VERSION') -lib = sysconfig.get_config_var('BINDIR') def write(doc, *parts): @@ -44,7 +41,6 @@ def find_version(*file_paths): macros = [ - ('_hypot', 'hypot'), ('M_PI', 'PI'), ('_USE_MATH_DEFINES', None), ('ISOLATION_AWARE_ENABLED', None), @@ -82,6 +78,8 @@ def find_version(*file_paths): # Platform sources sources.append(pth_join(platform_path, 'utilwin.cpp')) sources.append(pth_join(platform_path, 'platform.cpp')) + if sys.version_info < (3, 7): + macros.append(('_hypot', 'hypot')) else: sources.append(pth_join(platform_path, 'utilunix.cpp')) @@ -112,7 +110,7 @@ def build_extensions(self): e.extra_compile_args = compile_args elif compiler == 'msvc': for e in self.extensions: - e.define_macros = macros[2:] + e.define_macros = macros[1:] e.libraries = ['shell32'] super(Build, self).build_extensions() From 1cd310dabf11d987b292eb4bf856fb37206fd6ab Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 26 Oct 2019 23:29:25 +0800 Subject: [PATCH 047/118] Update Python 3.8 to stable version. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5b7dca392..4af647819 100644 --- a/.travis.yml +++ b/.travis.yml @@ -59,7 +59,7 @@ matrix: python: "3.7" - <<: *linux - python: "3.8-dev" + python: "3.8" - &osx os: osx @@ -90,7 +90,7 @@ matrix: env: PYTHON=3.7.0 - <<: *osx - env: PYTHON=3.8-dev + env: PYTHON=3.8.0 before_cache: - rm -rf $HOME/.cache/pip/log From 8795237497b0d73617cf241ee2501ef0846f2f6e Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 27 Oct 2019 20:58:21 +0800 Subject: [PATCH 048/118] Add Python 3.8 CI. --- appveyor.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index 338de3303..7a2f7af15 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,8 +10,14 @@ environment: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 PYTHON_DIR: C:\Python37-x64 COMPILER: mingw32 + # Cython not yet support - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON_DIR: C:\Python37-x64 + PYTHON_DEV: 3.8.0 + PYTHON_DIR: C:\Python38-x64 + COMPILER: mingw32 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PYTHON_DEV: 3.8.0 + PYTHON_DIR: C:\Python38-x64 COMPILER: msvc for: - matrix: @@ -42,6 +48,8 @@ for: only: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 install: + # Install Python-dev + - IF DEFINED PYTHON_DEV choco install -y python --version %PYTHON_DEV% # Environment variables - set Path=%MSYS_DIR%\mingw64\bin;%MSYS_DIR%\usr\bin;%Path% - set Path=%PYTHON_DIR%;%PYTHON_DIR%\Scripts;%Path% From 173d2b9e4676eb0918c35902ac3f1a1a335f3f29 Mon Sep 17 00:00:00 2001 From: Pace Willisson Date: Wed, 20 Nov 2019 09:00:27 -0500 Subject: [PATCH 049/118] typo in python quaternion_u --- cython/python_solvespace/slvs.pyx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 8eb7357d5..124b74b64 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -17,7 +17,7 @@ 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) + Slvs_QuaternionU(qw, qx, qy, qz, &x, &y, &z) return x, y, z From 35832930767b877a79f2fd78df70d7a1996aae1e Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Thu, 21 Nov 2019 13:53:11 +0800 Subject: [PATCH 050/118] Update AppVeyor Python version. --- appveyor.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index c6ff6d849..b14e60e82 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,12 +12,10 @@ environment: COMPILER: mingw32 # Cython not yet support # - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 -# PYTHON_DEV: 3.8.0 -# PYTHON_DIR: C:\Python38 +# PYTHON_DIR: C:\Python38-x64 # COMPILER: mingw32 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON_DEV: 3.8.0 - PYTHON_DIR: C:\Python38 + PYTHON_DIR: C:\Python38-x64 COMPILER: msvc for: - matrix: From 9d573178c096ee9a26976d24be493b5b761fd989 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 27 Nov 2019 14:27:17 +0800 Subject: [PATCH 051/118] Solve Homebrew error. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 25ffa8d0d..e3bea58df 100644 --- a/.travis.yml +++ b/.travis.yml @@ -65,7 +65,7 @@ jobs: env: PYTHON=3.6.0 before_install: - brew update - - brew upgrade pyenv + - brew upgrade pyenv || true - export PATH="/Users/travis/.pyenv/shims:${PATH}" - pyenv install ${PYTHON} - pyenv global ${PYTHON} From 6cb9ff92a42feb7eb6c0273c26e6e83faa89d8c7 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 29 Jan 2020 11:40:41 +0800 Subject: [PATCH 052/118] Typing patch. --- cython/python_solvespace/slvs.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index a00ea7c5a..40a2b35a5 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -45,8 +45,8 @@ class Params: class Entity: - FREE_IN_3D: ClassVar[Entity] - NONE: ClassVar[Entity] + FREE_IN_3D: ClassVar[Entity] = ... + NONE: ClassVar[Entity] = ... params: Params def is_3d(self) -> bool: From 5c0e66baaffc504570a2638c3fe36517e9f834b9 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 29 Jan 2020 11:46:58 +0800 Subject: [PATCH 053/118] Bump version. --- appveyor.yml | 3 +-- cython/python_solvespace/__init__.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b14e60e82..1916fa3a6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -68,8 +68,7 @@ for: after_build: # PyPI deployment - IF "%APPVEYOR_REPO_TAG%"=="true" - IF "%APPVEYOR_REPO_NAME%"=="KmolYuan/solvespace" - IF "%COMPILER%"=="mingw32" ( + IF "%APPVEYOR_REPO_NAME%"=="KmolYuan/solvespace" ( pip install twine && cd cython && python setup.py bdist_wheel && diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 39bfc9cfe..c238623e6 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.1.post0" +__version__ = "3.0.1.post1" from enum import IntEnum, auto from .slvs import ( From 05634f4f7be8c8d8324477a5130b1cd597220aa6 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 2 Feb 2020 21:44:32 +0800 Subject: [PATCH 054/118] Move docstring into stubs. --- cython/python_solvespace/slvs.pxd | 2 +- cython/python_solvespace/slvs.pyi | 235 ++++++++++++++++++++++++++---- cython/python_solvespace/slvs.pyx | 2 +- 3 files changed, 211 insertions(+), 28 deletions(-) diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index 7b1f0eab8..05cd83dff 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -255,7 +255,7 @@ cdef class SolverSystem: cpdef tuple params(self, Params p) cpdef int dof(self) cpdef object constraints(self) - cpdef list faileds(self) + cpdef list failures(self) cpdef int solve(self) cpdef Entity create_2d_base(self) cdef Slvs_hParam new_param(self, double val) nogil diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 40a2b35a5..f6984f5e3 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -8,6 +8,10 @@ def quaternion_u( qy: float, qz: float ) -> Tuple[float, float, float]: + """Input quaternion, return unit vector of U axis. + + Where `qw`, `qx`, `qy`, `qz` are corresponded to the W, X, Y, Z value of quaternion. + """ ... def quaternion_v( @@ -16,6 +20,10 @@ def quaternion_v( qy: float, qz: float ) -> Tuple[float, float, float]: + """Input quaternion, return unit vector of V axis. + + Signature is same as [quaternion_u](#quaternion_u). + """ ... def quaternion_n( @@ -24,6 +32,10 @@ def quaternion_n( qy: float, qz: float ) -> Tuple[float, float, float]: + """Input quaternion, return unit vector of normal. + + Signature is same as [quaternion_u](#quaternion_u). + """ ... def make_quaternion( @@ -34,139 +46,235 @@ def make_quaternion( vy: float, vz: float ) -> Tuple[float, float, float, float]: - ... + """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. + """ + ... class Params: + """The handles of parameters.""" + def __repr__(self) -> str: ... - class Entity: + """The handles of entities.""" + FREE_IN_3D: ClassVar[Entity] = ... NONE: ClassVar[Entity] = ... params: Params def is_3d(self) -> bool: + """Return True if this is a 3D entity.""" ... def is_none(self) -> bool: + """Return True if this is a empty entity.""" ... def is_point_2d(self) -> bool: + """Return True if this is a 2D point.""" ... def is_point_3d(self) -> bool: + """Return True if this is a 3D point.""" ... def is_point(self) -> bool: + """Return True if this is a point.""" ... def is_normal_2d(self) -> bool: + """Return True if this is a 2D normal.""" ... def is_normal_3d(self) -> bool: + """Return True if this is a 3D normal.""" ... def is_normal(self) -> bool: + """Return True if this is a normal.""" ... def is_distance(self) -> bool: + """Return True if this is a distance.""" ... def is_work_plane(self) -> bool: + """Return True if this is a work plane.""" ... def is_line_2d(self) -> bool: + """Return True if this is a 2D line.""" ... def is_line_3d(self) -> bool: + """Return True if this is a 3D line.""" ... def is_line(self) -> bool: + """Return True if this is a line.""" ... def is_cubic(self) -> bool: + """Return True if this is a cubic.""" ... def is_circle(self) -> bool: + """Return True if this is a circle.""" ... def is_arc(self) -> bool: + """Return True if this is a arc.""" ... def __repr__(self) -> str: ... - class SolverSystem: + """A solver system of Python-Solvespace. + + The operation of entities and constraints are using the methods of this class. + """ + def __init__(self) -> None: + """Initialization method. Create a solver system.""" ... def clear(self) -> None: + """Clear the system.""" ... def set_group(self, g: int) -> None: + """Set the current group (`g`).""" ... def group(self) -> int: + """Return the current group.""" ... def set_params(self, p: Params, params: Sequence[float]) -> None: + """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. + """ ... def params(self, p: Params) -> Tuple[float, ...]: + """Get the parameters from a [Params] handle (`p`) belong to this system. + The length of tuple is decided by handle. + """ ... def dof(self) -> int: + """Return the degrees of freedom of current group. + Only can be called after solving. + """ ... def constraints(self) -> Counter[str]: + """Return the number of each constraint type. + The name of constraints is represented by string. + """ ... - def faileds(self) -> List[int]: + def failures(self) -> List[int]: + """Return a list of failed constraint numbers.""" ... def solve(self) -> int: + """Start the solving, return the result flag.""" ... def create_2d_base(self) -> Entity: + """Create a 2D system on current group, + return the handle of work plane. + """ ... def add_point_2d(self, u: float, v: float, wp: Entity) -> Entity: + """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. + """ ... def add_point_3d(self, x: float, y: float, z: float) -> Entity: + """Add a 3D point then return the handle. + + Where `x`, `y`, `z` are corresponded to the value of X, Y, Z axis. + """ ... def add_normal_2d(self, wp: Entity) -> Entity: + """Add a 2D normal orthogonal to specific work plane (`wp`) + then return the handle. + """ ... def add_normal_3d(self, qw: float, qx: float, qy: float, qz: float) -> Entity: + """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. + """ ... def add_distance(self, d: float, wp: Entity) -> Entity: + """Add a distance to specific work plane (`wp`) then return the handle. + + Where `d` is distance value. + """ ... def add_line_2d(self, p1: Entity, p2: Entity, wp: Entity) -> Entity: + """Add a 2D line to specific work plane (`wp`) then return the handle. + + Where `p1` is the start point; `p2` is the end point. + """ ... def add_line_3d(self, p1: Entity, p2: Entity) -> Entity: + """Add a 3D line then return the handle. + + Where `p1` is the start point; `p2` is the end point. + """ ... def add_cubic(self, p1: Entity, p2: Entity, p3: Entity, p4: Entity, wp: Entity) -> Entity: + """Add a cubic curve to specific work plane (`wp`) then return the handle. + + Where `p1` to `p4` is the control points. + """ ... def add_arc(self, nm: Entity, ct: Entity, start: Entity, end: Entity, wp: Entity) -> Entity: + """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. + """ ... def add_circle(self, nm: Entity, ct: Entity, radius: Entity, wp: Entity) -> Entity: + """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. + """ ... def add_work_plane(self, origin: Entity, nm: Entity) -> Entity: + """Add a work plane then return the handle. + + Where `origin` is the origin point of the plane; + `nm` is the orthogonal normal. + """ ... def add_constraint( @@ -183,10 +291,25 @@ class SolverSystem: other: int = 0, other2: int = 0 ) -> None: + """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. + """ ... def coincident(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """Coincident two entities.""" + """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 | + """ ... def distance( @@ -196,11 +319,31 @@ class SolverSystem: value: float, wp: Entity = Entity.FREE_IN_3D ) -> None: - """Distance constraint between two entities.""" + """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 | + """ ... def equal(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """Equal constraint between two entities.""" + """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 | + """ ... def equal_included_angle( @@ -211,8 +354,8 @@ class SolverSystem: e4: Entity, wp: Entity ) -> None: - """Constraint that point 1 and line 1, point 2 and line 2 - must have same distance. + """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`. """ ... @@ -224,13 +367,13 @@ class SolverSystem: e4: Entity, wp: Entity ) -> None: - """Constraint that line 1 and line 2, line 3 and line 4 - must have same included angle. + """Constraint that point 1 (`e1`) and line 1 (`e2`), + point 2 (`e3`) and line 2 (`e4`) must have same distance on work plane `wp`. """ ... def ratio(self, e1: Entity, e2: Entity, value: float, wp: Entity) -> None: - """The ratio constraint between two lines.""" + """The ratio (`value`) constraint between two 2D lines (`e1` and `e2`).""" ... def symmetric( @@ -240,15 +383,26 @@ class SolverSystem: e3: Entity = Entity.NONE, wp: Entity = Entity.FREE_IN_3D ) -> None: - """Symmetric constraint between two points.""" + """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] | + """ ... def symmetric_h(self, e1: Entity, e2: Entity, wp: Entity) -> None: - """Symmetric constraint between two points with horizontal line.""" + """Symmetric constraint between two 2D points (`e1` and `e2`) + with horizontal line on the work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ ... def symmetric_v(self, e1: Entity, e2: Entity, wp: Entity) -> None: - """Symmetric constraint between two points with vertical line.""" + """Symmetric constraint between two 2D points (`e1` and `e2`) + with vertical line on the work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ ... def midpoint( @@ -257,45 +411,74 @@ class SolverSystem: e2: Entity, wp: Entity = Entity.FREE_IN_3D ) -> None: - """Midpoint constraint between a point and a line.""" + """Midpoint constraint between a point (`e1`) and + a line (`e2`) on work plane (`wp`). + """ ... def horizontal(self, e1: Entity, wp: Entity) -> None: - """Horizontal constraint of a 2d point.""" + """Horizontal constraint of a 2d point (`e1`) on + work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ ... def vertical(self, e1: Entity, wp: Entity) -> None: - """Vertical constraint of a 2d point.""" + """Vertical constraint of a 2d point (`e1`) on + work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ ... def diameter(self, e1: Entity, value: float, wp: Entity) -> None: - """Diameter constraint of a circular entities.""" + """Diameter (`value`) constraint of a circular entities. + + | Entity 1 (`e1`) | Work plane (`wp`) | + |:---------------:|:-----------------:| + | [is_arc] | Optional | + | [is_circle] | Optional | + """ ... def same_orientation(self, e1: Entity, e2: Entity) -> None: - """Equal orientation constraint between two 3d normals.""" + """Equal orientation constraint between two 3d normals (`e1` and `e2`).""" ... def angle(self, e1: Entity, e2: Entity, value: float, wp: Entity, inverse: bool = False) -> None: - """Degrees angle constraint between two 2d lines.""" + """Degrees angle (`value`) constraint between two 2d lines (`e1` and `e2`) + on the work plane (`wp` can not be [Entity.FREE_IN_3D]). + """ ... def perpendicular(self, e1: Entity, e2: Entity, wp: Entity, inverse: bool = False) -> None: - """Perpendicular constraint between two 2d lines.""" + """Perpendicular constraint between two 2d lines (`e1` and `e2`) + on the work plane (`wp` can not be [Entity.FREE_IN_3D]) with `inverse` option. + """ ... def parallel(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """Parallel constraint between two lines.""" + """Parallel constraint between two lines (`e1` and `e2`) on + the work plane (`wp`). + """ ... def tangent(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """Parallel constraint between two entities.""" + """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 | + """ ... def distance_proj(self, e1: Entity, e2: Entity, value: float) -> None: - """Projected distance constraint between two 3d points.""" + """Projected distance (`value`) constraint between + two 3d points (`e1` and `e2`). + """ ... def dragged(self, e1: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """Dragged constraint of a point.""" + """Dragged constraint of a point (`e1`) on the work plane (`wp`).""" ... diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 124b74b64..7560a3834 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -347,7 +347,7 @@ cdef class SolverSystem: cons_list.append(_NAME_OF_CONSTRAINTS[con.type]) return Counter(cons_list) - cpdef list faileds(self): + cpdef list failures(self): """Return the count of failed constraint.""" failed_list = [] cdef Slvs_hConstraint error From fe29a202ce42c42a46d8bd5139f4658cd5333a8d Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 2 Feb 2020 21:45:20 +0800 Subject: [PATCH 055/118] Bump version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index c238623e6..ef1e94528 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.1.post1" +__version__ = "3.0.1.post2" from enum import IntEnum, auto from .slvs import ( From ede91f49551eee8b08ae957060beb5020c902c02 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 22 Feb 2020 15:02:20 +0800 Subject: [PATCH 056/118] Move docstring to sources instead of stubs. --- cython/python_solvespace/__init__.py | 2 +- cython/python_solvespace/slvs.pyi | 216 +--------------------- cython/python_solvespace/slvs.pyx | 266 ++++++++++++++++++++++----- 3 files changed, 223 insertions(+), 261 deletions(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index ef1e94528..09d2a1611 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.1.post2" +__version__ = "3.0.1.post3" from enum import IntEnum, auto from .slvs import ( diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index f6984f5e3..a8501fa18 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -8,10 +8,6 @@ def quaternion_u( qy: float, qz: float ) -> Tuple[float, float, float]: - """Input quaternion, return unit vector of U axis. - - Where `qw`, `qx`, `qy`, `qz` are corresponded to the W, X, Y, Z value of quaternion. - """ ... def quaternion_v( @@ -20,10 +16,6 @@ def quaternion_v( qy: float, qz: float ) -> Tuple[float, float, float]: - """Input quaternion, return unit vector of V axis. - - Signature is same as [quaternion_u](#quaternion_u). - """ ... def quaternion_n( @@ -32,10 +24,6 @@ def quaternion_n( qy: float, qz: float ) -> Tuple[float, float, float]: - """Input quaternion, return unit vector of normal. - - Signature is same as [quaternion_u](#quaternion_u). - """ ... def make_quaternion( @@ -46,235 +34,132 @@ def make_quaternion( vy: float, vz: float ) -> Tuple[float, float, float, float]: - """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. - """ ... class Params: - - """The handles of parameters.""" - - def __repr__(self) -> str: - ... + pass class Entity: - """The handles of entities.""" - FREE_IN_3D: ClassVar[Entity] = ... NONE: ClassVar[Entity] = ... params: Params def is_3d(self) -> bool: - """Return True if this is a 3D entity.""" ... def is_none(self) -> bool: - """Return True if this is a empty entity.""" ... def is_point_2d(self) -> bool: - """Return True if this is a 2D point.""" ... def is_point_3d(self) -> bool: - """Return True if this is a 3D point.""" ... def is_point(self) -> bool: - """Return True if this is a point.""" ... def is_normal_2d(self) -> bool: - """Return True if this is a 2D normal.""" ... def is_normal_3d(self) -> bool: - """Return True if this is a 3D normal.""" ... def is_normal(self) -> bool: - """Return True if this is a normal.""" ... def is_distance(self) -> bool: - """Return True if this is a distance.""" ... def is_work_plane(self) -> bool: - """Return True if this is a work plane.""" ... def is_line_2d(self) -> bool: - """Return True if this is a 2D line.""" ... def is_line_3d(self) -> bool: - """Return True if this is a 3D line.""" ... def is_line(self) -> bool: - """Return True if this is a line.""" ... def is_cubic(self) -> bool: - """Return True if this is a cubic.""" ... def is_circle(self) -> bool: - """Return True if this is a circle.""" ... def is_arc(self) -> bool: - """Return True if this is a arc.""" - ... - - def __repr__(self) -> str: ... class SolverSystem: - """A solver system of Python-Solvespace. - - The operation of entities and constraints are using the methods of this class. - """ - def __init__(self) -> None: """Initialization method. Create a solver system.""" ... def clear(self) -> None: - """Clear the system.""" ... def set_group(self, g: int) -> None: - """Set the current group (`g`).""" ... def group(self) -> int: - """Return the current group.""" ... def set_params(self, p: Params, params: Sequence[float]) -> None: - """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. - """ ... def params(self, p: Params) -> Tuple[float, ...]: - """Get the parameters from a [Params] handle (`p`) belong to this system. - The length of tuple is decided by handle. - """ ... def dof(self) -> int: - """Return the degrees of freedom of current group. - Only can be called after solving. - """ ... def constraints(self) -> Counter[str]: - """Return the number of each constraint type. - The name of constraints is represented by string. - """ ... def failures(self) -> List[int]: - """Return a list of failed constraint numbers.""" ... def solve(self) -> int: - """Start the solving, return the result flag.""" ... def create_2d_base(self) -> Entity: - """Create a 2D system on current group, - return the handle of work plane. - """ ... def add_point_2d(self, u: float, v: float, wp: Entity) -> Entity: - """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. - """ ... def add_point_3d(self, x: float, y: float, z: float) -> Entity: - """Add a 3D point then return the handle. - - Where `x`, `y`, `z` are corresponded to the value of X, Y, Z axis. - """ ... def add_normal_2d(self, wp: Entity) -> Entity: - """Add a 2D normal orthogonal to specific work plane (`wp`) - then return the handle. - """ ... def add_normal_3d(self, qw: float, qx: float, qy: float, qz: float) -> Entity: - """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. - """ ... def add_distance(self, d: float, wp: Entity) -> Entity: - """Add a distance to specific work plane (`wp`) then return the handle. - - Where `d` is distance value. - """ ... def add_line_2d(self, p1: Entity, p2: Entity, wp: Entity) -> Entity: - """Add a 2D line to specific work plane (`wp`) then return the handle. - - Where `p1` is the start point; `p2` is the end point. - """ ... def add_line_3d(self, p1: Entity, p2: Entity) -> Entity: - """Add a 3D line then return the handle. - - Where `p1` is the start point; `p2` is the end point. - """ ... def add_cubic(self, p1: Entity, p2: Entity, p3: Entity, p4: Entity, wp: Entity) -> Entity: - """Add a cubic curve to specific work plane (`wp`) then return the handle. - - Where `p1` to `p4` is the control points. - """ ... def add_arc(self, nm: Entity, ct: Entity, start: Entity, end: Entity, wp: Entity) -> Entity: - """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. - """ ... def add_circle(self, nm: Entity, ct: Entity, radius: Entity, wp: Entity) -> Entity: - """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. - """ ... def add_work_plane(self, origin: Entity, nm: Entity) -> Entity: - """Add a work plane then return the handle. - - Where `origin` is the origin point of the plane; - `nm` is the orthogonal normal. - """ ... def add_constraint( @@ -291,25 +176,9 @@ class SolverSystem: other: int = 0, other2: int = 0 ) -> None: - """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. - """ ... def coincident(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """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 | - """ ... def distance( @@ -319,31 +188,9 @@ class SolverSystem: value: float, wp: Entity = Entity.FREE_IN_3D ) -> None: - """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 | - """ ... def equal(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """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 | - """ ... def equal_included_angle( @@ -354,9 +201,6 @@ class SolverSystem: e4: Entity, wp: Entity ) -> None: - """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`. - """ ... def equal_point_to_line( @@ -367,13 +211,9 @@ class SolverSystem: e4: Entity, wp: Entity ) -> None: - """Constraint that point 1 (`e1`) and line 1 (`e2`), - point 2 (`e3`) and line 2 (`e4`) must have same distance on work plane `wp`. - """ ... def ratio(self, e1: Entity, e2: Entity, value: float, wp: Entity) -> None: - """The ratio (`value`) constraint between two 2D lines (`e1` and `e2`).""" ... def symmetric( @@ -383,26 +223,12 @@ class SolverSystem: e3: Entity = Entity.NONE, wp: Entity = Entity.FREE_IN_3D ) -> None: - """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] | - """ ... def symmetric_h(self, e1: Entity, e2: Entity, wp: Entity) -> None: - """Symmetric constraint between two 2D points (`e1` and `e2`) - with horizontal line on the work plane (`wp` can not be [Entity.FREE_IN_3D]). - """ ... def symmetric_v(self, e1: Entity, e2: Entity, wp: Entity) -> None: - """Symmetric constraint between two 2D points (`e1` and `e2`) - with vertical line on the work plane (`wp` can not be [Entity.FREE_IN_3D]). - """ ... def midpoint( @@ -411,74 +237,34 @@ class SolverSystem: e2: Entity, wp: Entity = Entity.FREE_IN_3D ) -> None: - """Midpoint constraint between a point (`e1`) and - a line (`e2`) on work plane (`wp`). - """ ... def horizontal(self, e1: Entity, wp: Entity) -> None: - """Horizontal constraint of a 2d point (`e1`) on - work plane (`wp` can not be [Entity.FREE_IN_3D]). - """ ... def vertical(self, e1: Entity, wp: Entity) -> None: - """Vertical constraint of a 2d point (`e1`) on - work plane (`wp` can not be [Entity.FREE_IN_3D]). - """ ... def diameter(self, e1: Entity, value: float, wp: Entity) -> None: - """Diameter (`value`) constraint of a circular entities. - - | Entity 1 (`e1`) | Work plane (`wp`) | - |:---------------:|:-----------------:| - | [is_arc] | Optional | - | [is_circle] | Optional | - """ ... def same_orientation(self, e1: Entity, e2: Entity) -> None: - """Equal orientation constraint between two 3d normals (`e1` and `e2`).""" ... def angle(self, e1: Entity, e2: Entity, value: float, wp: Entity, inverse: bool = False) -> None: - """Degrees angle (`value`) constraint between two 2d lines (`e1` and `e2`) - on the work plane (`wp` can not be [Entity.FREE_IN_3D]). - """ ... def perpendicular(self, e1: Entity, e2: Entity, wp: Entity, inverse: bool = False) -> None: - """Perpendicular constraint between two 2d lines (`e1` and `e2`) - on the work plane (`wp` can not be [Entity.FREE_IN_3D]) with `inverse` option. - """ ... def parallel(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """Parallel constraint between two lines (`e1` and `e2`) on - the work plane (`wp`). - """ ... def tangent(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """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 | - """ ... def distance_proj(self, e1: Entity, e2: Entity, value: float) -> None: - """Projected distance (`value`) constraint between - two 3d points (`e1` and `e2`). - """ ... def dragged(self, e1: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: - """Dragged constraint of a point (`e1`) on the work plane (`wp`).""" ... diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 7560a3834..af4056733 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -16,24 +16,42 @@ 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 @@ -144,7 +162,7 @@ _NAME_OF_CONSTRAINTS = { cdef class Entity: - """Python object to handle a pointer of 'Slvs_hEntity'.""" + """The handles of entities.""" FREE_IN_3D = _E_FREE_IN_3D NONE = _E_NONE @@ -180,51 +198,67 @@ cdef class Entity: ) 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: @@ -239,7 +273,11 @@ cdef class Entity: cdef class SolverSystem: - """Python object of 'Slvs_System'.""" + """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 @@ -282,6 +320,7 @@ cdef class SolverSystem: 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() @@ -307,15 +346,18 @@ cdef class SolverSystem: self.sys.params = self.sys.entities = self.sys.constraints = 0 cpdef void set_group(self, size_t g): - """Set the current group by integer.""" + """Set the current group (`g`).""" self.g = g cpdef int group(self): - """Return the current group by integer.""" + """Return the current group.""" return self.g cpdef void set_params(self, Params p, object params): - """Set the parameters by Params object and sequence object.""" + """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): @@ -328,7 +370,10 @@ cdef class SolverSystem: i += 1 cpdef tuple params(self, Params p): - """Get the parameters by Params object.""" + """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: @@ -336,11 +381,15 @@ cdef class SolverSystem: return tuple(param_list) cpdef int dof(self): - """Return the DOF of system.""" + """Return the degrees of freedom of current group. + Only can be called after solving. + """ return self.sys.dof cpdef object constraints(self): - """Return the list of all constraints.""" + """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: @@ -348,7 +397,7 @@ cdef class SolverSystem: return Counter(cons_list) cpdef list failures(self): - """Return the count of failed constraint.""" + """Return a list of failed constraint numbers.""" failed_list = [] cdef Slvs_hConstraint error for error in self.failed_list: @@ -356,7 +405,7 @@ cdef class SolverSystem: return failed_list cpdef int solve(self): - """Solve the system.""" + """Start the solving, return the result flag.""" # Parameters self.sys.param = PyMem_Malloc(self.param_list.size() * sizeof(Slvs_Param)) # Entities @@ -378,7 +427,9 @@ cdef class SolverSystem: return self.sys.result cpdef Entity create_2d_base(self): - """Create a basic 2D system and return the work plane.""" + """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) @@ -397,7 +448,11 @@ cdef class SolverSystem: return self.sys.entities cpdef Entity add_point_2d(self, double u, double v, Entity wp): - """Add 2D point.""" + """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") @@ -409,7 +464,10 @@ cdef class SolverSystem: return Entity.create(&e, 2) cpdef Entity add_point_3d(self, double x, double y, double z): - """Add 3D point.""" + """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) @@ -419,7 +477,9 @@ cdef class SolverSystem: return Entity.create(&e, 3) cpdef Entity add_normal_2d(self, Entity wp): - """Add a 2D normal.""" + """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) @@ -427,7 +487,11 @@ cdef class SolverSystem: return Entity.create(&e, 0) cpdef Entity add_normal_3d(self, double qw, double qx, double qy, double qz): - """Add a 3D normal.""" + """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) @@ -437,7 +501,10 @@ cdef class SolverSystem: return Entity.create(&self.entity_list.back(), 4) cpdef Entity add_distance(self, double d, Entity wp): - """Add a 2D distance.""" + """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") @@ -447,7 +514,10 @@ cdef class SolverSystem: return Entity.create(&self.entity_list.back(), 1) cpdef Entity add_line_2d(self, Entity p1, Entity p2, Entity wp): - """Add a 2D line.""" + """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(): @@ -460,7 +530,10 @@ cdef class SolverSystem: return Entity.create(&self.entity_list.back(), 0) cpdef Entity add_line_3d(self, Entity p1, Entity p2): - """Add a 3D line.""" + """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(): @@ -471,7 +544,11 @@ cdef class SolverSystem: 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.""" + """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(): @@ -488,7 +565,11 @@ cdef class SolverSystem: 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.""" + """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(): @@ -504,7 +585,12 @@ cdef class SolverSystem: 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.""" + """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(): @@ -519,7 +605,11 @@ cdef class SolverSystem: return Entity.create(&self.entity_list.back(), 0) cpdef Entity add_work_plane(self, Entity origin, Entity nm): - """Add a 3D work plane.""" + """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: @@ -542,7 +632,14 @@ cdef class SolverSystem: int other = 0, int other2 = 0 ): - """Add customized constraint.""" + """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") @@ -576,7 +673,15 @@ cdef class SolverSystem: ##### cpdef void coincident(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D): - """Coincident two entities.""" + """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, @@ -600,7 +705,17 @@ cdef class SolverSystem: double value, Entity wp = _E_FREE_IN_3D ): - """Distance constraint between two entities.""" + """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 @@ -617,7 +732,18 @@ cdef class SolverSystem: 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.""" + """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) @@ -637,8 +763,9 @@ cdef class SolverSystem: Entity e4, Entity wp ): - """Constraint that line 1 and line 2, line 3 and line 4 - must have same included angle. + """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") @@ -656,8 +783,9 @@ cdef class SolverSystem: Entity e4, Entity wp ): - """Constraint that point 1 and line 1, point 2 and line 2 - must have same distance. + """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") @@ -667,7 +795,9 @@ cdef class SolverSystem: 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.""" + """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(): @@ -683,7 +813,14 @@ cdef class SolverSystem: Entity e3 = _E_NONE, Entity wp = _E_FREE_IN_3D ): - """Symmetric constraint between two points.""" + """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: @@ -696,7 +833,10 @@ cdef class SolverSystem: 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.""" + """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(): @@ -705,7 +845,10 @@ cdef class SolverSystem: 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.""" + """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(): @@ -719,14 +862,18 @@ cdef class SolverSystem: Entity e2, Entity wp = _E_FREE_IN_3D ): - """Midpoint constraint between a point and a line.""" + """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): - """Horizontal constraint of a 2d point.""" + """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(): @@ -735,7 +882,9 @@ cdef class SolverSystem: raise TypeError(f"unsupported entities: {e1}, {wp}") cpdef void vertical(self, Entity e1, Entity wp): - """Vertical constraint of a 2d point.""" + """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(): @@ -744,7 +893,13 @@ cdef class SolverSystem: raise TypeError(f"unsupported entities: {e1}, {wp}") cpdef void diameter(self, Entity e1, double value, Entity wp): - """Diameter constraint of a circular entities.""" + """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(): @@ -754,7 +909,9 @@ cdef class SolverSystem: raise TypeError(f"unsupported entities: {e1}, {wp}") cpdef void same_orientation(self, Entity e1, Entity e2): - """Equal orientation constraint between two 3d normals.""" + """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) @@ -762,7 +919,9 @@ cdef class SolverSystem: 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.""" + """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(): @@ -772,7 +931,10 @@ cdef class SolverSystem: 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.""" + """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(): @@ -782,14 +944,26 @@ cdef class SolverSystem: 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.""" + """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.""" + """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") @@ -804,7 +978,9 @@ cdef class SolverSystem: 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.""" + """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) @@ -812,7 +988,7 @@ cdef class SolverSystem: raise TypeError(f"unsupported entities: {e1}, {e2}") cpdef void dragged(self, Entity e1, Entity wp = _E_FREE_IN_3D): - """Dragged constraint of a point.""" + """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: From 6f7c26460b5ffb956a28b369f803b87710e48f9b Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 22 Feb 2020 16:11:05 +0800 Subject: [PATCH 057/118] Fix deployment. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6b253be31..bb8d4c770 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,7 +48,7 @@ jobs: script: sudo ./.travis/deploy-snap.sh edge,beta skip_cleanup: true on: - branch: master + repo: solvespace/solvespace tags: true - <<: *deploy-snap name: Snap arm64 From 88bab6243915a03d01b7098b99d39b039d991a4a Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 7 Jul 2020 14:55:03 +0800 Subject: [PATCH 058/118] Update CI settings. --- appveyor.yml | 17 +++++++---------- cython/setup.py | 7 +++++++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 77c9fbc0a..6626e1626 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,17 +5,16 @@ environment: matrix: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON_DIR: C:\Python36-x64 + PY: 36 COMPILER: mingw32 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON_DIR: C:\Python37-x64 + PY: 37 COMPILER: mingw32 - # Cython not yet support -# - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 -# PYTHON_DIR: C:\Python38-x64 -# COMPILER: mingw32 - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PYTHON_DIR: C:\Python38-x64 + PY: 38 + COMPILER: mingw32 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + PY: 38 COMPILER: msvc for: - matrix: @@ -49,6 +48,7 @@ for: # Install Python-dev - IF DEFINED PYTHON_DEV choco install -y python --version %PYTHON_DEV% # Environment variables + - set PYTHON_DIR=C:\Python%PY%-x64 - set Path=%MSYS_DIR%\mingw64\bin;%MSYS_DIR%\usr\bin;%Path% - set Path=%PYTHON_DIR%;%PYTHON_DIR%\Scripts;%Path% # Show Python @@ -60,9 +60,6 @@ for: - cython\platform\set_pycompiler %PYTHON_DIR% %COMPILER% # Install modules - pip install -r cython\requirements.txt - # Show tool kits - - gcc --version - - mingw32-make --version build_script: - cd cython && python setup.py test && cd .. after_build: diff --git a/cython/setup.py b/cython/setup.py index eb70b19e1..a90fd87d0 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -57,6 +57,10 @@ def find_version(*file_paths): '-fPIC', '-std=c++17', ] +link_args = ['-static-libgcc', '-static-libstdc++', + '-Wl,-Bstatic,--whole-archive', + '-lwinpthread', + '-Wl,--no-whole-archive'] sources = [ pth_join('python_solvespace', 'slvs.pyx'), pth_join(src_path, 'util.cpp'), @@ -108,10 +112,13 @@ def build_extensions(self): for e in self.extensions: e.define_macros = macros e.extra_compile_args = compile_args + if compiler == 'mingw32': + e.extra_link_args = link_args elif compiler == 'msvc': for e in self.extensions: e.define_macros = macros[1:] e.libraries = ['shell32'] + e.extra_compile_args = ['/O2'] super(Build, self).build_extensions() def run(self): From e728c45f368e7a491b2ab5cbe41db507b616deb5 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 7 Jul 2020 14:55:48 +0800 Subject: [PATCH 059/118] Bump version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 09d2a1611..a33d0eed3 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.1.post3" +__version__ = "3.0.2" from enum import IntEnum, auto from .slvs import ( From 25fe77f777f95f3d05cf6efd1613f8990e708d0b Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 8 Jul 2020 14:48:06 +0800 Subject: [PATCH 060/118] Update compile script. --- cython/setup.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cython/setup.py b/cython/setup.py index a90fd87d0..67bc3305b 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -70,22 +70,21 @@ def find_version(*file_paths): pth_join(src_path, 'constraint.cpp'), pth_join(src_path, 'system.cpp'), pth_join(src_path, 'lib.cpp'), + pth_join(platform_path, 'platform.cpp'), ] if {'sdist', 'bdist'} & set(sys.argv): - for s in ('utilwin', 'utilunix', 'platform'): - sources.append(pth_join(platform_path, f'{s}.cpp')) + sources.append(pth_join(platform_path, 'platform.cpp')) elif system() == 'Windows': # Disable format warning compile_args.append('-Wno-format') # Solvespace arguments macros.append(('WIN32', None)) # Platform sources - sources.append(pth_join(platform_path, 'utilwin.cpp')) sources.append(pth_join(platform_path, 'platform.cpp')) if sys.version_info < (3, 7): macros.append(('_hypot', 'hypot')) else: - sources.append(pth_join(platform_path, 'utilunix.cpp')) + macros.append(('UNIX_DATADIR', '"solvespace"')) def copy_source(dry_run): From 7c90159880248eed20fb98abf26978925473007d Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 8 Jul 2020 15:58:13 +0800 Subject: [PATCH 061/118] Update msvc version. --- appveyor.yml | 5 +++-- cython/platform/cygwinccompiler.diff | 10 +++------- cython/setup.py | 2 -- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 6626e1626..ca9014c58 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,8 @@ clone_depth: 1 environment: MSYS_DIR: C:\msys64 matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 + CPP: true - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 PY: 36 COMPILER: mingw32 @@ -19,7 +20,7 @@ environment: for: - matrix: only: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013 + - CPP: true before_build: - git submodule update --init - set tag=x%APPVEYOR_REPO_TAG_NAME% diff --git a/cython/platform/cygwinccompiler.diff b/cython/platform/cygwinccompiler.diff index 6b2726dc4..d04d7960b 100644 --- a/cython/platform/cygwinccompiler.diff +++ b/cython/platform/cygwinccompiler.diff @@ -1,6 +1,6 @@ --- cygwinccompiler.py +++ cygwinccompiler.py -@@ -82,7 +82,25 @@ def get_msvcr(): +@@ -84,7 +84,21 @@ def get_msvcr(): elif msc_ver == '1600': # VS2010 / MSVC 10.0 return ['msvcr100'] @@ -14,13 +14,9 @@ + # 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': ++ elif 1910 <= int(msc_ver) <= 1916: + return ['vcruntime140'] -+ elif msc_ver == '1914': -+ return ['vcruntime140'] -+ elif msc_ver == '1915': -+ return ['vcruntime140'] -+ elif msc_ver == '1916': ++ elif 1920 <= int(msc_ver) <= 1926: + return ['vcruntime140'] else: raise ValueError("Unknown MS Compiler version %s " % msc_ver) diff --git a/cython/setup.py b/cython/setup.py index 67bc3305b..cf1545762 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -79,8 +79,6 @@ def find_version(*file_paths): compile_args.append('-Wno-format') # Solvespace arguments macros.append(('WIN32', None)) - # Platform sources - sources.append(pth_join(platform_path, 'platform.cpp')) if sys.version_info < (3, 7): macros.append(('_hypot', 'hypot')) else: From 8506f432f026916e4d8159e3f80efde4001a5297 Mon Sep 17 00:00:00 2001 From: Yuan Date: Thu, 9 Jul 2020 11:40:27 +0800 Subject: [PATCH 062/118] Update README.md --- cython/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/README.md b/cython/README.md index 259013697..dc73b2dd4 100644 --- a/cython/README.md +++ b/cython/README.md @@ -6,7 +6,7 @@ # python-solvespace -Python library from solver of SolveSpace. +Python library from the solver of SolveSpace, an open source CAD software. + [Python API](https://pyslvs-ui.readthedocs.io/en/stable/python-solvespace-api/) + [C API](https://github.com/solvespace/solvespace/blob/master/exposed/DOC.txt) From 27bfe0fee31525c8cbd578a5200e9d703cb89fe2 Mon Sep 17 00:00:00 2001 From: Yuan Date: Thu, 9 Jul 2020 11:41:11 +0800 Subject: [PATCH 063/118] Remove version badge. --- cython/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/cython/README.md b/cython/README.md index dc73b2dd4..d34b75960 100644 --- a/cython/README.md +++ b/cython/README.md @@ -1,4 +1,3 @@ -[![Version](https://img.shields.io/badge/version-3.0.0-yellow.svg)](https://github.com/KmolYuan/solvespace/releases/latest) [![Build status](https://ci.appveyor.com/api/projects/status/b2o8jw7xnfqghqr5?svg=true)](https://ci.appveyor.com/project/KmolYuan/solvespace) [![Build status](https://img.shields.io/travis/KmolYuan/solvespace.svg?logo=travis)](https://travis-ci.org/KmolYuan/solvespace) [![PyPI](https://img.shields.io/pypi/v/python-solvespace.svg)](https://pypi.org/project/python-solvespace/) From 9197b07a1f348d3c1f0199c8d748446735fe1df5 Mon Sep 17 00:00:00 2001 From: Yuan Date: Mon, 20 Jul 2020 13:32:19 +0800 Subject: [PATCH 064/118] Remove unused requirements. --- cython/requirements.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/cython/requirements.txt b/cython/requirements.txt index d1e61c4a5..f6629e024 100644 --- a/cython/requirements.txt +++ b/cython/requirements.txt @@ -1,3 +1 @@ -setuptools -wheel cython From bc9b4d0929ff5813aefe7e737cbe9f4a12dfa210 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 31 Jul 2020 14:33:54 +0800 Subject: [PATCH 065/118] Remove signatures in docstring. --- cython/python_solvespace/slvs.pyx | 16 ++++++++-------- cython/setup.py | 9 +++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index af4056733..02d84dad2 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# cython: language_level=3, embedsignature=True, cdivision=True +# cython: language_level=3 """Wrapper source code of Solvespace. @@ -18,7 +18,7 @@ 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 + Where `qw`, `qx`, `qy`, `qz` are corresponded to the W, X, Y, Z value of quaternion. """ cdef double x, y, z @@ -354,7 +354,7 @@ cdef class SolverSystem: return self.g cpdef void set_params(self, Params p, object params): - """Set the parameters from a [Params] handle (`p`) belong to this + """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. """ @@ -370,7 +370,7 @@ cdef class SolverSystem: i += 1 cpdef tuple params(self, Params p): - """Get the parameters from a [Params] handle (`p`) belong to this + """Get the parameters from a [Params] handle (`p`) belong to this system. The length of tuple is decided by handle. """ @@ -636,7 +636,7 @@ cdef class SolverSystem: 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 + `p1` and `p2` represent point entities; `e1` to `e4` represent other types of entity; `other` and `other2` are control options of the constraint. """ @@ -764,7 +764,7 @@ cdef class SolverSystem: 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 + line 3 (`e3`) and line 4 (`e4`) must have same included angle on work plane `wp`. """ if wp is _E_FREE_IN_3D: @@ -784,7 +784,7 @@ cdef class SolverSystem: Entity wp ): """Constraint that point 1 (`e1`) and line 1 (`e2`), - point 2 (`e3`) and line 2 (`e4`) must have same distance on work + point 2 (`e3`) and line 2 (`e4`) must have same distance on work plane `wp`. """ if wp is _E_FREE_IN_3D: @@ -795,7 +795,7 @@ cdef class SolverSystem: 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 + """The ratio (`value`) constraint between two 2D lines (`e1` and `e2`). """ if wp is _E_FREE_IN_3D: diff --git a/cython/setup.py b/cython/setup.py index cf1545762..e321ca230 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -83,6 +83,7 @@ def find_version(*file_paths): macros.append(('_hypot', 'hypot')) else: macros.append(('UNIX_DATADIR', '"solvespace"')) +compiler_directives = {'binding': True, 'cdivision': True} def copy_source(dry_run): @@ -105,14 +106,14 @@ def copy_source(dry_run): class Build(build_ext): def build_extensions(self): compiler = self.compiler.compiler_type - if compiler in {'mingw32', 'unix'}: - for e in self.extensions: + for e in self.extensions: + e.cython_directives = compiler_directives + if compiler in {'mingw32', 'unix'}: e.define_macros = macros e.extra_compile_args = compile_args if compiler == 'mingw32': e.extra_link_args = link_args - elif compiler == 'msvc': - for e in self.extensions: + elif compiler == 'msvc': e.define_macros = macros[1:] e.libraries = ['shell32'] e.extra_compile_args = ['/O2'] From 68f9bf8f39110a76296131403b85ac5b6dfd94d3 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 31 Jul 2020 15:15:23 +0800 Subject: [PATCH 066/118] Ignore deployment in fork repo. --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index 0350ec5db..b37161e62 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,6 +46,7 @@ jobs: skip_cleanup: true on: branch: master + repo: solvespace/solvespace tags: false - provider: script script: sudo ./.travis/deploy-snap.sh edge,beta From fe5446b64ab6879ca6e447ae20290710cd3cfd1d Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 31 Jul 2020 16:17:36 +0800 Subject: [PATCH 067/118] Compile MiMalloc by setup.py. --- .travis.yml | 3 +++ appveyor.yml | 1 + cython/setup.py | 60 +++++++++++++++++++++++++++++++++++-------------- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/.travis.yml b/.travis.yml index b37161e62..088404972 100644 --- a/.travis.yml +++ b/.travis.yml @@ -69,6 +69,8 @@ jobs: update: true packages: - patchelf + before_install: + - git submodule update --init -- extlib/mimalloc install: &python-install - python3 -m pip install -r cython/requirements.txt script: &python-script @@ -95,6 +97,7 @@ jobs: language: generic env: PYTHON=3.6.0 before_install: + - git submodule update --init -- extlib/mimalloc - brew update - brew upgrade pyenv || true - export PATH="/Users/travis/.pyenv/shims:${PATH}" diff --git a/appveyor.yml b/appveyor.yml index ca9014c58..01ba7e99d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -46,6 +46,7 @@ for: only: - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 install: + - git submodule update --init -- extlib/mimalloc # Install Python-dev - IF DEFINED PYTHON_DEV choco install -y python --version %PYTHON_DEV% # Environment variables diff --git a/cython/setup.py b/cython/setup.py index e321ca230..ec070bf33 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -18,9 +18,13 @@ from distutils import file_util, dir_util from platform import system -include_path = pth_join('python_solvespace', 'include') -src_path = pth_join('python_solvespace', 'src') +m_path = 'python_solvespace' +include_path = pth_join(m_path, 'include') +src_path = pth_join(m_path, 'src') platform_path = pth_join(src_path, 'platform') +mimalloc_path = pth_join(m_path, 'extlib', 'mimalloc') +mimalloc_include_path = pth_join(mimalloc_path, 'include') +mimalloc_src_path = pth_join(mimalloc_path, 'src') def write(doc, *parts): @@ -60,17 +64,33 @@ def find_version(*file_paths): link_args = ['-static-libgcc', '-static-libstdc++', '-Wl,-Bstatic,--whole-archive', '-lwinpthread', + '-lbcrypt', + '-lpsapi', '-Wl,--no-whole-archive'] sources = [ - pth_join('python_solvespace', 'slvs.pyx'), + pth_join(m_path, 'slvs.pyx'), pth_join(src_path, 'util.cpp'), pth_join(src_path, 'entity.cpp'), pth_join(src_path, 'expr.cpp'), - pth_join(src_path, 'constrainteq.cpp'), pth_join(src_path, 'constraint.cpp'), + pth_join(src_path, 'constrainteq.cpp'), pth_join(src_path, 'system.cpp'), pth_join(src_path, 'lib.cpp'), pth_join(platform_path, 'platform.cpp'), + # MiMalloc + pth_join(mimalloc_src_path, 'stats.c'), + pth_join(mimalloc_src_path, 'random.c'), + pth_join(mimalloc_src_path, 'os.c'), + pth_join(mimalloc_src_path, 'arena.c'), + pth_join(mimalloc_src_path, 'region.c'), + pth_join(mimalloc_src_path, 'segment.c'), + pth_join(mimalloc_src_path, 'page.c'), + pth_join(mimalloc_src_path, 'alloc.c'), + pth_join(mimalloc_src_path, 'alloc-aligned.c'), + pth_join(mimalloc_src_path, 'alloc-posix.c'), + pth_join(mimalloc_src_path, 'heap.c'), + pth_join(mimalloc_src_path, 'options.c'), + pth_join(mimalloc_src_path, 'init.c'), ] if {'sdist', 'bdist'} & set(sys.argv): sources.append(pth_join(platform_path, 'platform.cpp')) @@ -88,18 +108,22 @@ def find_version(*file_paths): def copy_source(dry_run): dir_util.copy_tree(pth_join('..', 'include'), include_path, dry_run=dry_run) + dir_util.copy_tree(pth_join('..', 'extlib', 'mimalloc', 'include'), include_path, dry_run=dry_run) dir_util.mkpath(src_path) - for root, _, files in walk(pth_join('..', 'src')): - for f in files: - if not f.endswith('.h'): - continue - f = pth_join(root, f) - f_new = f.replace('..', 'python_solvespace') - if not isdir(dirname(f_new)): - dir_util.mkpath(dirname(f_new)) - file_util.copy_file(f, f_new, dry_run=dry_run) + dir_util.mkpath(mimalloc_src_path) + for path in (pth_join('..', 'src'), pth_join('..', 'extlib', 'mimalloc', 'src')): + for root, _, files in walk(path): + for f in files: + if not (f.endswith('.h') or f.endswith('.c')): + continue + f = pth_join(root, f) + f_new = f.replace('..', m_path) + if not isdir(dirname(f_new)): + dir_util.mkpath(dirname(f_new)) + file_util.copy_file(f, f_new, dry_run=dry_run) for f in sources[1:]: - file_util.copy_file(f.replace('python_solvespace', '..'), f, dry_run=dry_run) + file_util.copy_file(f.replace(m_path, '..'), f, dry_run=dry_run) + # Create an empty header open(pth_join(platform_path, 'config.h'), 'a').close() @@ -120,13 +144,14 @@ def build_extensions(self): super(Build, self).build_extensions() def run(self): - has_src = isdir(include_path) and isdir(src_path) + has_src = isdir(include_path) and isdir(src_path) and isdir(mimalloc_path) if not has_src: copy_source(self.dry_run) super(Build, self).run() if not has_src: dir_util.remove_tree(include_path, dry_run=self.dry_run) dir_util.remove_tree(src_path, dry_run=self.dry_run) + dir_util.remove_tree(mimalloc_path, dry_run=self.dry_run) class PackSource(sdist): @@ -136,11 +161,12 @@ def run(self): if not self.keep_temp: dir_util.remove_tree(include_path, dry_run=self.dry_run) dir_util.remove_tree(src_path, dry_run=self.dry_run) + dir_util.remove_tree(mimalloc_path, dry_run=self.dry_run) setup( name="python_solvespace", - version=find_version('python_solvespace', '__init__.py'), + version=find_version(m_path, '__init__.py'), author=__author__, author_email=__email__, description="Python library of Solvespace.", @@ -153,7 +179,7 @@ def run(self): "python_solvespace.slvs", sources, language="c++", - include_dirs=[include_path, src_path, platform_path] + include_dirs=[include_path, src_path, mimalloc_include_path, mimalloc_src_path] )], cmdclass={'build_ext': Build, 'sdist': PackSource}, zip_safe=False, From 7ad7b5b7fbe5f5126e8af9c845f19dac89a30cb1 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 31 Jul 2020 17:05:09 +0800 Subject: [PATCH 068/118] Fix static linking of stdlibs. --- cython/.gitignore | 1 + cython/setup.py | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/cython/.gitignore b/cython/.gitignore index e254ee788..c5fabf457 100644 --- a/cython/.gitignore +++ b/cython/.gitignore @@ -16,6 +16,7 @@ python_solvespace/*.cpp python_solvespace/src/ python_solvespace/include/ +python_solvespace/extlib/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/cython/setup.py b/cython/setup.py index ec070bf33..1388f368d 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -22,7 +22,8 @@ include_path = pth_join(m_path, 'include') src_path = pth_join(m_path, 'src') platform_path = pth_join(src_path, 'platform') -mimalloc_path = pth_join(m_path, 'extlib', 'mimalloc') +extlib_path = pth_join(m_path, 'extlib') +mimalloc_path = pth_join(extlib_path, 'mimalloc') mimalloc_include_path = pth_join(mimalloc_path, 'include') mimalloc_src_path = pth_join(mimalloc_path, 'src') @@ -64,9 +65,10 @@ def find_version(*file_paths): link_args = ['-static-libgcc', '-static-libstdc++', '-Wl,-Bstatic,--whole-archive', '-lwinpthread', + '-Wl,--no-whole-archive', '-lbcrypt', '-lpsapi', - '-Wl,--no-whole-archive'] + '-Wl,-Bdynamic'] sources = [ pth_join(m_path, 'slvs.pyx'), pth_join(src_path, 'util.cpp'), @@ -144,14 +146,14 @@ def build_extensions(self): super(Build, self).build_extensions() def run(self): - has_src = isdir(include_path) and isdir(src_path) and isdir(mimalloc_path) + has_src = isdir(include_path) and isdir(src_path) and isdir(extlib_path) if not has_src: copy_source(self.dry_run) super(Build, self).run() if not has_src: dir_util.remove_tree(include_path, dry_run=self.dry_run) dir_util.remove_tree(src_path, dry_run=self.dry_run) - dir_util.remove_tree(mimalloc_path, dry_run=self.dry_run) + dir_util.remove_tree(extlib_path, dry_run=self.dry_run) class PackSource(sdist): @@ -161,7 +163,7 @@ def run(self): if not self.keep_temp: dir_util.remove_tree(include_path, dry_run=self.dry_run) dir_util.remove_tree(src_path, dry_run=self.dry_run) - dir_util.remove_tree(mimalloc_path, dry_run=self.dry_run) + dir_util.remove_tree(extlib_path, dry_run=self.dry_run) setup( From b67af9e2f45c2bd28b966aebf58886a02def1ba1 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 31 Jul 2020 19:44:44 +0800 Subject: [PATCH 069/118] Fix the error on clang. --- cython/setup.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cython/setup.py b/cython/setup.py index 1388f368d..b9a483ef3 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -60,8 +60,9 @@ def find_version(*file_paths): '-Wno-write-strings', '-fpermissive', '-fPIC', - '-std=c++17', ] +if system() != 'Darwin': + compile_args.append('-std=c++17') link_args = ['-static-libgcc', '-static-libstdc++', '-Wl,-Bstatic,--whole-archive', '-lwinpthread', @@ -142,7 +143,7 @@ def build_extensions(self): elif compiler == 'msvc': e.define_macros = macros[1:] e.libraries = ['shell32'] - e.extra_compile_args = ['/O2'] + e.extra_compile_args = ['/O2', '/std:c++17'] super(Build, self).build_extensions() def run(self): From 2c081a643f70a1f3b3518c0fa9974614cdee77dc Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 31 Jul 2020 19:51:44 +0800 Subject: [PATCH 070/118] Patch MSVC libraries. --- cython/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/setup.py b/cython/setup.py index b9a483ef3..e9c8fcc02 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -142,7 +142,7 @@ def build_extensions(self): e.extra_link_args = link_args elif compiler == 'msvc': e.define_macros = macros[1:] - e.libraries = ['shell32'] + e.libraries = ['shell32', 'advapi32', 'Ws2_32'] e.extra_compile_args = ['/O2', '/std:c++17'] super(Build, self).build_extensions() From f7f3d69e8fda500a975e9d168fe2ff07f2d0faeb Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 5 Oct 2020 12:00:10 +0800 Subject: [PATCH 071/118] Rename test suit. --- cython/setup.py | 2 +- cython/{tests => test}/__init__.py | 0 cython/test/__main__.py | 4 ++++ cython/{tests/__main__.py => test/test_slvs.py} | 5 ----- 4 files changed, 5 insertions(+), 6 deletions(-) rename cython/{tests => test}/__init__.py (100%) create mode 100644 cython/test/__main__.py rename cython/{tests/__main__.py => test/test_slvs.py} (99%) diff --git a/cython/setup.py b/cython/setup.py index e9c8fcc02..ef9ed4581 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -188,7 +188,7 @@ def run(self): zip_safe=False, python_requires=">=3.6", install_requires=read('requirements.txt').splitlines(), - test_suite='tests', + test_suite='test', classifiers=[ "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", diff --git a/cython/tests/__init__.py b/cython/test/__init__.py similarity index 100% rename from cython/tests/__init__.py rename to cython/test/__init__.py diff --git a/cython/test/__main__.py b/cython/test/__main__.py new file mode 100644 index 000000000..98c0f1ab6 --- /dev/null +++ b/cython/test/__main__.py @@ -0,0 +1,4 @@ +from unittest import defaultTestLoader, TextTestRunner + +if __name__ == '__main__': + TextTestRunner().run(defaultTestLoader.discover('test')) diff --git a/cython/tests/__main__.py b/cython/test/test_slvs.py similarity index 99% rename from cython/tests/__main__.py rename to cython/test/test_slvs.py index 0f44f8a38..16439ee84 100644 --- a/cython/tests/__main__.py +++ b/cython/test/test_slvs.py @@ -7,7 +7,6 @@ __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -import unittest from unittest import TestCase from math import radians from python_solvespace import ResultFlag, SolverSystem, make_quaternion @@ -242,7 +241,3 @@ def test_pydemo(self): x, = sys.params(d307.params) self.assertAlmostEqual(17, x, 4) self.assertEqual(6, sys.dof()) - - -if __name__ == '__main__': - unittest.main() From 8324af6b7f5d7b28e1820829f01cdd2b3b254b9f Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 5 Oct 2020 12:08:13 +0800 Subject: [PATCH 072/118] Update configuration. --- cython/platform/cygwinccompiler.diff | 2 +- cython/test/test_slvs.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cython/platform/cygwinccompiler.diff b/cython/platform/cygwinccompiler.diff index d04d7960b..b025ae200 100644 --- a/cython/platform/cygwinccompiler.diff +++ b/cython/platform/cygwinccompiler.diff @@ -16,7 +16,7 @@ + return ['vcruntime140'] + elif 1910 <= int(msc_ver) <= 1916: + return ['vcruntime140'] -+ elif 1920 <= int(msc_ver) <= 1926: ++ elif 1920 <= int(msc_ver) <= 1927: + return ['vcruntime140'] else: raise ValueError("Unknown MS Compiler version %s " % msc_ver) diff --git a/cython/test/test_slvs.py b/cython/test/test_slvs.py index 16439ee84..a491db5da 100644 --- a/cython/test/test_slvs.py +++ b/cython/test/test_slvs.py @@ -145,10 +145,10 @@ def test_nut_cracker(self): 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. + """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. From 0716991f6fbb091d0e7ffd47c03f737d2a4c8298 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 5 Oct 2020 12:09:35 +0800 Subject: [PATCH 073/118] Bump wrapper version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index a33d0eed3..711721161 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.2" +__version__ = "3.0.3" from enum import IntEnum, auto from .slvs import ( From 46979db60d305aae2a945270e1a715eb024573ac Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 5 Oct 2020 14:29:02 +0800 Subject: [PATCH 074/118] Try to fix the CLang problem. --- .travis.yml | 2 +- appveyor.yml | 2 +- cython/setup.py | 115 ++++++++++++++++++++++++++++-------------------- 3 files changed, 69 insertions(+), 50 deletions(-) diff --git a/.travis.yml b/.travis.yml index 088404972..92a007325 100644 --- a/.travis.yml +++ b/.travis.yml @@ -111,7 +111,7 @@ jobs: after_success: # PyPI deployment - if [[ "$TRAVIS_REPO_SLUG" == "KmolYuan/solvespace" && -n "$TRAVIS_TAG" ]]; then - python3 -m pip install twine; + python3 -m pip install wheel twine; cd cython; python3 setup.py bdist_wheel; python3 -m twine upload dist/*.whl --skip-existing; diff --git a/appveyor.yml b/appveyor.yml index 01ba7e99d..d861e94d2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -68,7 +68,7 @@ for: # PyPI deployment - IF "%APPVEYOR_REPO_TAG%"=="true" IF "%APPVEYOR_REPO_NAME%"=="KmolYuan/solvespace" ( - pip install twine && + pip install wheel twine && cd cython && python setup.py bdist_wheel && twine upload dist\*.whl --skip-existing diff --git a/cython/setup.py b/cython/setup.py index ef9ed4581..a16017866 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -9,7 +9,7 @@ import sys from os import walk -from os.path import dirname, isdir, join as pth_join +from os.path import dirname, isdir, join, abspath import re import codecs from setuptools import setup, Extension, find_packages @@ -19,22 +19,23 @@ from platform import system m_path = 'python_solvespace' -include_path = pth_join(m_path, 'include') -src_path = pth_join(m_path, 'src') -platform_path = pth_join(src_path, 'platform') -extlib_path = pth_join(m_path, 'extlib') -mimalloc_path = pth_join(extlib_path, 'mimalloc') -mimalloc_include_path = pth_join(mimalloc_path, 'include') -mimalloc_src_path = pth_join(mimalloc_path, 'src') +include_path = join(m_path, 'include') +src_path = join(m_path, 'src') +platform_path = join(src_path, 'platform') +extlib_path = join(m_path, 'extlib') +mimalloc_path = join(extlib_path, 'mimalloc') +mimalloc_include_path = join(mimalloc_path, 'include') +mimalloc_src_path = join(mimalloc_path, 'src') +build_dir = join(abspath(dirname(__file__)), 'build') def write(doc, *parts): - with codecs.open(pth_join(*parts), 'w') as f: + with codecs.open(join(*parts), 'w') as f: f.write(doc) def read(*parts): - with codecs.open(pth_join(*parts), 'r') as f: + with codecs.open(join(*parts), 'r') as f: return f.read() @@ -60,9 +61,12 @@ def find_version(*file_paths): '-Wno-write-strings', '-fpermissive', '-fPIC', + '-std=c++17', +] +compile_args_msvc = [ + '/O2', + '/std:c++17', ] -if system() != 'Darwin': - compile_args.append('-std=c++17') link_args = ['-static-libgcc', '-static-libstdc++', '-Wl,-Bstatic,--whole-archive', '-lwinpthread', @@ -71,32 +75,34 @@ def find_version(*file_paths): '-lpsapi', '-Wl,-Bdynamic'] sources = [ - pth_join(m_path, 'slvs.pyx'), - pth_join(src_path, 'util.cpp'), - pth_join(src_path, 'entity.cpp'), - pth_join(src_path, 'expr.cpp'), - pth_join(src_path, 'constraint.cpp'), - pth_join(src_path, 'constrainteq.cpp'), - pth_join(src_path, 'system.cpp'), - pth_join(src_path, 'lib.cpp'), - pth_join(platform_path, 'platform.cpp'), + join(m_path, 'slvs.pyx'), + join(src_path, 'util.cpp'), + join(src_path, 'entity.cpp'), + join(src_path, 'expr.cpp'), + join(src_path, 'constraint.cpp'), + join(src_path, 'constrainteq.cpp'), + join(src_path, 'system.cpp'), + join(src_path, 'lib.cpp'), + join(platform_path, 'platform.cpp'), +] +mimalloc_sources = [ # MiMalloc - pth_join(mimalloc_src_path, 'stats.c'), - pth_join(mimalloc_src_path, 'random.c'), - pth_join(mimalloc_src_path, 'os.c'), - pth_join(mimalloc_src_path, 'arena.c'), - pth_join(mimalloc_src_path, 'region.c'), - pth_join(mimalloc_src_path, 'segment.c'), - pth_join(mimalloc_src_path, 'page.c'), - pth_join(mimalloc_src_path, 'alloc.c'), - pth_join(mimalloc_src_path, 'alloc-aligned.c'), - pth_join(mimalloc_src_path, 'alloc-posix.c'), - pth_join(mimalloc_src_path, 'heap.c'), - pth_join(mimalloc_src_path, 'options.c'), - pth_join(mimalloc_src_path, 'init.c'), + join(mimalloc_src_path, 'stats.c'), + join(mimalloc_src_path, 'random.c'), + join(mimalloc_src_path, 'os.c'), + join(mimalloc_src_path, 'arena.c'), + join(mimalloc_src_path, 'region.c'), + join(mimalloc_src_path, 'segment.c'), + join(mimalloc_src_path, 'page.c'), + join(mimalloc_src_path, 'alloc.c'), + join(mimalloc_src_path, 'alloc-aligned.c'), + join(mimalloc_src_path, 'alloc-posix.c'), + join(mimalloc_src_path, 'heap.c'), + join(mimalloc_src_path, 'options.c'), + join(mimalloc_src_path, 'init.c'), ] if {'sdist', 'bdist'} & set(sys.argv): - sources.append(pth_join(platform_path, 'platform.cpp')) + sources.append(join(platform_path, 'platform.cpp')) elif system() == 'Windows': # Disable format warning compile_args.append('-Wno-format') @@ -110,24 +116,26 @@ def find_version(*file_paths): def copy_source(dry_run): - dir_util.copy_tree(pth_join('..', 'include'), include_path, dry_run=dry_run) - dir_util.copy_tree(pth_join('..', 'extlib', 'mimalloc', 'include'), include_path, dry_run=dry_run) + dir_util.copy_tree(join('..', 'include'), include_path, dry_run=dry_run) + dir_util.copy_tree(join('..', 'extlib', 'mimalloc', 'include'), + mimalloc_include_path, + dry_run=dry_run) dir_util.mkpath(src_path) dir_util.mkpath(mimalloc_src_path) - for path in (pth_join('..', 'src'), pth_join('..', 'extlib', 'mimalloc', 'src')): + for path in (join('..', 'src'), join('..', 'extlib', 'mimalloc', 'src')): for root, _, files in walk(path): for f in files: if not (f.endswith('.h') or f.endswith('.c')): continue - f = pth_join(root, f) + f = join(root, f) f_new = f.replace('..', m_path) if not isdir(dirname(f_new)): dir_util.mkpath(dirname(f_new)) file_util.copy_file(f, f_new, dry_run=dry_run) - for f in sources[1:]: + for f in sources[1:] + mimalloc_sources: file_util.copy_file(f.replace(m_path, '..'), f, dry_run=dry_run) # Create an empty header - open(pth_join(platform_path, 'config.h'), 'a').close() + open(join(platform_path, 'config.h'), 'a').close() class Build(build_ext): @@ -135,6 +143,8 @@ def build_extensions(self): compiler = self.compiler.compiler_type for e in self.extensions: e.cython_directives = compiler_directives + e.libraries = ['mimalloc'] + e.library_dirs = [build_dir] if compiler in {'mingw32', 'unix'}: e.define_macros = macros e.extra_compile_args = compile_args @@ -142,15 +152,24 @@ def build_extensions(self): e.extra_link_args = link_args elif compiler == 'msvc': e.define_macros = macros[1:] - e.libraries = ['shell32', 'advapi32', 'Ws2_32'] - e.extra_compile_args = ['/O2', '/std:c++17'] - super(Build, self).build_extensions() - - def run(self): + e.libraries.extend(['shell32', 'advapi32', 'Ws2_32']) + e.extra_compile_args = compile_args_msvc has_src = isdir(include_path) and isdir(src_path) and isdir(extlib_path) if not has_src: copy_source(self.dry_run) - super(Build, self).run() + # Pre-build MiMalloc + if compiler in {'mingw32', 'unix'}: + args = ['-fPIC'] + else: + args = [] + objects = self.compiler.compile( + mimalloc_sources, + extra_postargs=args, + include_dirs=[mimalloc_include_path, mimalloc_src_path] + ) + self.compiler.create_static_lib(objects, 'mimalloc', target_lang='c', + output_dir=build_dir) + super(Build, self).build_extensions() if not has_src: dir_util.remove_tree(include_path, dry_run=self.dry_run) dir_util.remove_tree(src_path, dry_run=self.dry_run) @@ -176,7 +195,7 @@ def run(self): long_description=read("README.md"), long_description_content_type='text/markdown', url="https://github.com/KmolYuan/solvespace", - packages=find_packages(exclude=('tests',)), + packages=find_packages(exclude=('test',)), package_data={'': ["*.pyi", "*.pxd"], 'python_solvespace': ['py.typed']}, ext_modules=[Extension( "python_solvespace.slvs", From 6fb8209fcd6e66a8c83ea17a22d07cdb8a8f56d6 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 7 Oct 2020 10:25:50 +0800 Subject: [PATCH 075/118] Fix build path for msvc. --- .travis.yml | 1 - cython/setup.py | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 92a007325..ac4de922c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,7 +60,6 @@ jobs: - &linux stage: deploy os: linux - sudo: required dist: bionic language: python python: "3.6" diff --git a/cython/setup.py b/cython/setup.py index a16017866..4627a8df2 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -9,7 +9,7 @@ import sys from os import walk -from os.path import dirname, isdir, join, abspath +from os.path import dirname, isdir, join import re import codecs from setuptools import setup, Extension, find_packages @@ -26,7 +26,7 @@ mimalloc_path = join(extlib_path, 'mimalloc') mimalloc_include_path = join(mimalloc_path, 'include') mimalloc_src_path = join(mimalloc_path, 'src') -build_dir = join(abspath(dirname(__file__)), 'build') +build_dir = 'build' def write(doc, *parts): @@ -167,6 +167,7 @@ def build_extensions(self): extra_postargs=args, include_dirs=[mimalloc_include_path, mimalloc_src_path] ) + dir_util.mkpath(build_dir) self.compiler.create_static_lib(objects, 'mimalloc', target_lang='c', output_dir=build_dir) super(Build, self).build_extensions() From 0e229f7814847eb2717c2123c798ecf2346f8528 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 7 Oct 2020 13:19:56 +0800 Subject: [PATCH 076/118] Bump version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 711721161..7cae90e53 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.3" +__version__ = "3.0.3.post0" from enum import IntEnum, auto from .slvs import ( From be1146365819dad45de83892b5d0887adf80076c Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 20 Oct 2020 12:21:37 +0800 Subject: [PATCH 077/118] Fix missing source files. --- cython/MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/MANIFEST.in b/cython/MANIFEST.in index a3dcb24d1..048558b49 100644 --- a/cython/MANIFEST.in +++ b/cython/MANIFEST.in @@ -1,3 +1,3 @@ include requirements.txt include README.md -recursive-include . *.h +recursive-include . *.h *.c From 04d064437699292331d05d2cd3f4dfc4b54d1105 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 20 Oct 2020 12:22:14 +0800 Subject: [PATCH 078/118] Bump version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 7cae90e53..7483fec36 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.3.post0" +__version__ = "3.0.3.post1" from enum import IntEnum, auto from .slvs import ( From 34ac524a3c6fac49e19fa09e8434287440bedd51 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 3 Nov 2020 18:06:10 +0800 Subject: [PATCH 079/118] Add Python 3.9 for Travis. --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index ac4de922c..0246a23c3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -89,6 +89,8 @@ jobs: python: "3.7" - <<: *linux python: "3.8" + - <<: *linux + python: "3.9" - &osx stage: deploy os: osx @@ -119,6 +121,8 @@ jobs: env: PYTHON=3.7.0 - <<: *osx env: PYTHON=3.8.0 + - <<: *osx + env: PYTHON=3.9.0 before_cache: - rm -rf $HOME/.cache/pip/log cache: From 848501bf469e9a2d7929de135e1c68fb9da73465 Mon Sep 17 00:00:00 2001 From: Constantin Mateescu Date: Thu, 11 Feb 2021 17:51:18 +0200 Subject: [PATCH 080/118] Fixed ratio constraints. --- cython/README.md | 4 ++-- cython/python_solvespace/slvs.pyx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cython/README.md b/cython/README.md index d34b75960..e511a5bb7 100644 --- a/cython/README.md +++ b/cython/README.md @@ -24,10 +24,10 @@ Build and install the module: python setup.py install ``` -Run unit test: +Run unit tests: ```bash -python tests +python test ``` Uninstall the module: diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 02d84dad2..931726bee 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -801,8 +801,7 @@ cdef class SolverSystem: 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) + self.add_constraint(SLVS_C_LENGTH_RATIO, wp, value, _E_NONE, _E_NONE, e1, e2) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") From 0710a8c3c52c0c9865c5a45f0b546bda2315389f Mon Sep 17 00:00:00 2001 From: Yuan Date: Fri, 12 Feb 2021 11:00:07 +0800 Subject: [PATCH 081/118] Update MSVC version. --- cython/platform/cygwinccompiler.diff | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/platform/cygwinccompiler.diff b/cython/platform/cygwinccompiler.diff index b025ae200..e7b1f9c14 100644 --- a/cython/platform/cygwinccompiler.diff +++ b/cython/platform/cygwinccompiler.diff @@ -16,7 +16,7 @@ + return ['vcruntime140'] + elif 1910 <= int(msc_ver) <= 1916: + return ['vcruntime140'] -+ elif 1920 <= int(msc_ver) <= 1927: ++ elif 1920 <= int(msc_ver) <= 1928: + return ['vcruntime140'] else: raise ValueError("Unknown MS Compiler version %s " % msc_ver) From 5ab9518c47df81c5dd365d0fe7710fbee1cc7d17 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 12 Feb 2021 11:10:27 +0800 Subject: [PATCH 082/118] Clean up to accept new structure. --- .gitignore | 1 + .travis.yml | 130 --------------------------------------------------- appveyor.yml | 82 -------------------------------- 3 files changed, 1 insertion(+), 212 deletions(-) delete mode 100644 .travis.yml delete mode 100644 appveyor.yml diff --git a/.gitignore b/.gitignore index 39493ffab..67ed3891b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ /debian/libslvs1-dev/ /obj-*/ /*.slvs +.vscode/ .idea/* diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 0246a23c3..000000000 --- a/.travis.yml +++ /dev/null @@ -1,130 +0,0 @@ -git: - submodules: false -jobs: - allow_failures: - - stage: deploy - name: Snap arm64 - include: - - stage: test - name: "Debian" - os: linux - dist: bionic - language: c - install: ./.travis/install-debian.sh - script: ./.travis/build-debian.sh - - stage: deploy - name: "OSX" - os: osx - osx_image: xcode8.3 - language: c - install: ./.travis/install-macos.sh - # the awk command is a workaround for https://github.com/travis-ci/travis-ci/issues/4704. - script: ./.travis/build-macos.sh | awk '/.{0,32}/ {print $0}' - deploy: - provider: releases - api_key: - secure: dDlkIawHcODlW9B/20/cQCtzeoocvs0hKuNngRKXKqzXLWTRq33oq/B7+39tAixWbmv6exTpijiKrRNFiSCW5Z4iwHLwaRD4XJznxw63e/Hus/dxg2Tvqx7XFpkCz8mT1Z+gZQE5YxAngeZPpI/sZbZtF1UO3yH5eLeeokZ15p26ZskQUPoYuzrTgTzYL3XfpG3F+20rNBawH1ycsCTVD/08/n31d2m3CrKAsbW7er92ek6w4fzKr7NW8WeXjrPJETVpw5fQg1Od3pRGW8dPQaJcvKQEogMp8Mm0ETYd0qigg89/giBz7QwOgmAWQ4dH+DfZH4Ojl//127QztBolMvyDMQBykWrtJoGcij05sT6K2IJr2FHeUBO12MAEdjiVvhQj3DtTzjPiZAHHDBSLWxLKWWhlhHE4pq7g1MQhqXkaAHI2BLNzwLmaowbMT0bECf9yfz6xx18h6XPQFX44oOktraobVALFlyHqeKa8zdcUt22LF6uAL1m5dxL0tny3eXCIPE4UH/RZgua/cHV9G3cUvKQa/QnFSLRhvWVSbGB+7YsHouBJcsUOOW1gmd5442XuC7mpppccRldh+GSxUk6TBJRAx7TeQ0ybDUaoco9MUqp2twv3KreR2+8Q12PDaAhfQVNEGdF3wTm1sShImjCN4VN3eSLlBEbve1QRQXM= - skip_cleanup: true - file: build/SolveSpace.dmg - on: - repo: solvespace/solvespace - tags: true - - &deploy-snap - stage: deploy - name: Snap amd64 - os: linux - arch: amd64 - dist: bionic - addons: - snaps: - - name: snapcraft - confinement: classic - script: ./.travis/build-snap.sh - deploy: - - provider: script - script: sudo ./.travis/deploy-snap.sh edge - skip_cleanup: true - on: - branch: master - repo: solvespace/solvespace - tags: false - - provider: script - script: sudo ./.travis/deploy-snap.sh edge,beta - skip_cleanup: true - on: - repo: solvespace/solvespace - tags: true - - <<: *deploy-snap - name: Snap arm64 - arch: arm64 - - &linux - stage: deploy - os: linux - dist: bionic - language: python - python: "3.6" - addons: - apt: - update: true - packages: - - patchelf - before_install: - - git submodule update --init -- extlib/mimalloc - install: &python-install - - python3 -m pip install -r cython/requirements.txt - script: &python-script - - cd cython && python3 setup.py test && cd - - before_deploy: cd cython - deploy: - - provider: pypi - user: $TWINE_USERNAME - password: $TWINE_PASSWORD - skip_cleanup: true - skip_existing: true - distributions: sdist - on: - repo: KmolYuan/solvespace - tags: true - - <<: *linux - python: "3.7" - - <<: *linux - python: "3.8" - - <<: *linux - python: "3.9" - - &osx - stage: deploy - os: osx - osx_image: xcode10 - language: generic - env: PYTHON=3.6.0 - before_install: - - git submodule update --init -- extlib/mimalloc - - brew update - - brew upgrade pyenv || true - - export PATH="/Users/travis/.pyenv/shims:${PATH}" - - pyenv install ${PYTHON} - - pyenv global ${PYTHON} - - python3 -m pip install pip -U - - python3 --version - - python3 -m pip --version - install: *python-install - script: *python-script - after_success: - # PyPI deployment - - if [[ "$TRAVIS_REPO_SLUG" == "KmolYuan/solvespace" && -n "$TRAVIS_TAG" ]]; then - python3 -m pip install wheel twine; - cd cython; - python3 setup.py bdist_wheel; - python3 -m twine upload dist/*.whl --skip-existing; - fi - - <<: *osx - env: PYTHON=3.7.0 - - <<: *osx - env: PYTHON=3.8.0 - - <<: *osx - env: PYTHON=3.9.0 -before_cache: - - rm -rf $HOME/.cache/pip/log -cache: - directories: - - $HOME/.cache/pip diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index d861e94d2..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,82 +0,0 @@ -version: '{build}' -clone_depth: 1 -environment: - MSYS_DIR: C:\msys64 - matrix: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - CPP: true - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PY: 36 - COMPILER: mingw32 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PY: 37 - COMPILER: mingw32 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PY: 38 - COMPILER: mingw32 - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - PY: 38 - COMPILER: msvc -for: - - matrix: - only: - - CPP: true - before_build: - - git submodule update --init - - set tag=x%APPVEYOR_REPO_TAG_NAME% - - if %tag:~,2% == xv (set BUILD_TYPE=RelWithDebInfo) else (set BUILD_TYPE=Debug) - - mkdir build - - cmake -G"Visual Studio 14" -Tv140 -Bbuild -H. - build_script: - - msbuild "build\src\solvespace.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - - msbuild "build\src\solvespace-cli.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - - msbuild "build\test\solvespace-testsuite.vcxproj" /verbosity:minimal /property:Configuration=%BUILD_TYPE% /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" - test_script: - - build\bin\%BUILD_TYPE%\solvespace-testsuite.exe - deploy: - - provider: GitHub - auth_token: - secure: P9/pf2nM+jlWKe7pCjMp41HycBNP/+5AsmE/TETrDUoBOa/9WFHelqdVFrbRn9IC - description: "" - artifact: solvespace.exe,solvespace-cli.exe,solvespace.pdb - on: - APPVEYOR_REPO_NAME: solvespace/solvespace - APPVEYOR_REPO_TAG: true - - matrix: - only: - - APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2019 - install: - - git submodule update --init -- extlib/mimalloc - # Install Python-dev - - IF DEFINED PYTHON_DEV choco install -y python --version %PYTHON_DEV% - # Environment variables - - set PYTHON_DIR=C:\Python%PY%-x64 - - set Path=%MSYS_DIR%\mingw64\bin;%MSYS_DIR%\usr\bin;%Path% - - set Path=%PYTHON_DIR%;%PYTHON_DIR%\Scripts;%Path% - # Show Python - - python --version - - pip --version - # Upgrade setuptools - - pip install setuptools -U - # Set Python compiler to MinGW - - cython\platform\set_pycompiler %PYTHON_DIR% %COMPILER% - # Install modules - - pip install -r cython\requirements.txt - build_script: - - cd cython && python setup.py test && cd .. - after_build: - # PyPI deployment - - IF "%APPVEYOR_REPO_TAG%"=="true" - IF "%APPVEYOR_REPO_NAME%"=="KmolYuan/solvespace" ( - pip install wheel twine && - cd cython && - python setup.py bdist_wheel && - twine upload dist\*.whl --skip-existing - ) -artifacts: - - path: build\bin\%BUILD_TYPE%\solvespace.exe - name: solvespace.exe - - path: build\bin\%BUILD_TYPE%\solvespace-cli.exe - name: solvespace-cli.exe - - path: build\bin\%BUILD_TYPE%\solvespace.pdb - name: solvespace.pdb From e4a305492e09b573515822111106914d8aaa0843 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 12 Feb 2021 11:15:04 +0800 Subject: [PATCH 083/118] Merge from original repo. --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 67ed3891b..a387fd9ef 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ /obj-*/ /*.slvs .vscode/ -.idea/* +.idea/ From d8c69901d3ccbaa504da450f0e65db515681c300 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 12 Feb 2021 14:21:07 +0800 Subject: [PATCH 084/118] Apply PEP 517. --- cython/MANIFEST.in | 2 +- cython/README.md | 5 ++- cython/platform/set_pycompiler.bat | 5 ++- cython/pyproject.toml | 3 ++ cython/requirements.txt | 1 - cython/setup.cfg | 46 +++++++++++++++++++++ cython/setup.py | 66 ++++++------------------------ 7 files changed, 69 insertions(+), 59 deletions(-) create mode 100644 cython/pyproject.toml delete mode 100644 cython/requirements.txt create mode 100644 cython/setup.cfg diff --git a/cython/MANIFEST.in b/cython/MANIFEST.in index 048558b49..400ce8828 100644 --- a/cython/MANIFEST.in +++ b/cython/MANIFEST.in @@ -1,3 +1,3 @@ -include requirements.txt +include pyproject.toml setup.cfg include README.md recursive-include . *.h *.c diff --git a/cython/README.md b/cython/README.md index e511a5bb7..1c0454ecf 100644 --- a/cython/README.md +++ b/cython/README.md @@ -21,7 +21,10 @@ pip install python-solvespace Build and install the module: ```bash -python setup.py install +pip install python-solvespace +# From repository +git submodule update --init +pip install -e . ``` Run unit tests: diff --git a/cython/platform/set_pycompiler.bat b/cython/platform/set_pycompiler.bat index 3e42cf8a0..8b8513efc 100644 --- a/cython/platform/set_pycompiler.bat +++ b/cython/platform/set_pycompiler.bat @@ -16,8 +16,9 @@ echo compiler=%COMPILER%>> "%DISTUTILS%" echo patched file "%DISTUTILS%" REM Apply the patch of "cygwinccompiler.py". REM Unix "patch" command of Msys. -patch -N "%PYTHON_DIR%\lib\distutils\cygwinccompiler.py" "%HERE%\cygwinccompiler.diff" -patch -N "%PYTHON_DIR%\include\pyconfig.h" "%HERE%\pyconfig.diff" +set patch="C:\Program Files\Git\usr\bin\patch.exe" +%patch% -N "%PYTHON_DIR%\lib\distutils\cygwinccompiler.py" "%HERE%\cygwinccompiler.diff" +%patch% -N "%PYTHON_DIR%\include\pyconfig.h" "%HERE%\pyconfig.diff" REM Copy "vcruntime140.dll" to "libs". copy "%PYTHON_DIR%\vcruntime140.dll" "%PYTHON_DIR%\libs" diff --git a/cython/pyproject.toml b/cython/pyproject.toml new file mode 100644 index 000000000..10376dcbe --- /dev/null +++ b/cython/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["wheel", "setuptools"] +build-backend = "setuptools.build_meta" diff --git a/cython/requirements.txt b/cython/requirements.txt deleted file mode 100644 index f6629e024..000000000 --- a/cython/requirements.txt +++ /dev/null @@ -1 +0,0 @@ -cython diff --git a/cython/setup.cfg b/cython/setup.cfg new file mode 100644 index 000000000..06d6d07fb --- /dev/null +++ b/cython/setup.cfg @@ -0,0 +1,46 @@ +[metadata] +name = python_solvespace +version = attr: python_solvespace.__version__ +description = Python library of Solvespace. +long_description = file: README.md +long_description_content_type = text/markdown +keywords = cad,mechanical-engineering,2d,3d +license = GPLv3+ +author = Yuan Chang +author_email = pyslvs@gmail.com +url = https://github.com/KmolYuan/solvespace +classifiers = + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Cython + Topic :: Scientific/Engineering + License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) + Operating System :: OS Independent + Typing :: Typed + +[options] +zip_safe = False +packages = find: +python_requires = >=3.6 +setup_requires = + cython + +[options.package_data] +* = *.pyi, *.pxd, *.pyx +python_solvespace = py.typed + +[options.packages.find] +exclude = + test + +[mypy] +pretty = True +show_error_codes = True +show_column_numbers = True +ignore_missing_imports = True +allow_redefinition = True +warn_redundant_casts = True +warn_unreachable = True +strict_equality = True diff --git a/cython/setup.py b/cython/setup.py index 4627a8df2..9cddb0b68 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -10,9 +10,7 @@ import sys from os import walk from os.path import dirname, isdir, join -import re -import codecs -from setuptools import setup, Extension, find_packages +from setuptools import setup, Extension from setuptools.command.build_ext import build_ext from setuptools.command.sdist import sdist from distutils import file_util, dir_util @@ -27,25 +25,6 @@ mimalloc_include_path = join(mimalloc_path, 'include') mimalloc_src_path = join(mimalloc_path, 'src') build_dir = 'build' - - -def write(doc, *parts): - with codecs.open(join(*parts), 'w') as f: - f.write(doc) - - -def read(*parts): - with codecs.open(join(*parts), 'r') as f: - return f.read() - - -def find_version(*file_paths): - m = re.search(r"^__version__ = ['\"]([^'\"]*)['\"]", read(*file_paths), re.M) - if m: - return m.group(1) - raise RuntimeError("Unable to find version string.") - - macros = [ ('M_PI', 'PI'), ('_USE_MATH_DEFINES', None), @@ -90,8 +69,9 @@ def find_version(*file_paths): join(mimalloc_src_path, 'stats.c'), join(mimalloc_src_path, 'random.c'), join(mimalloc_src_path, 'os.c'), + join(mimalloc_src_path, 'bitmap.c'), join(mimalloc_src_path, 'arena.c'), - join(mimalloc_src_path, 'region.c'), + join(mimalloc_src_path, 'segment-cache.c'), join(mimalloc_src_path, 'segment.c'), join(mimalloc_src_path, 'page.c'), join(mimalloc_src_path, 'alloc.c'), @@ -139,6 +119,7 @@ def copy_source(dry_run): class Build(build_ext): + def build_extensions(self): compiler = self.compiler.compiler_type for e in self.extensions: @@ -178,6 +159,7 @@ def build_extensions(self): class PackSource(sdist): + def run(self): copy_source(self.dry_run) super(PackSource, self).run() @@ -187,34 +169,10 @@ def run(self): dir_util.remove_tree(extlib_path, dry_run=self.dry_run) -setup( - name="python_solvespace", - version=find_version(m_path, '__init__.py'), - author=__author__, - author_email=__email__, - description="Python library of Solvespace.", - long_description=read("README.md"), - long_description_content_type='text/markdown', - url="https://github.com/KmolYuan/solvespace", - packages=find_packages(exclude=('test',)), - package_data={'': ["*.pyi", "*.pxd"], 'python_solvespace': ['py.typed']}, - ext_modules=[Extension( - "python_solvespace.slvs", - sources, - language="c++", - include_dirs=[include_path, src_path, mimalloc_include_path, mimalloc_src_path] - )], - cmdclass={'build_ext': Build, 'sdist': PackSource}, - zip_safe=False, - python_requires=">=3.6", - install_requires=read('requirements.txt').splitlines(), - test_suite='test', - classifiers=[ - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Cython", - "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", - "Operating System :: OS Independent", - ] -) +setup(ext_modules=[Extension( + "python_solvespace.slvs", + sources, + language="c++", + include_dirs=[include_path, src_path, mimalloc_include_path, + mimalloc_src_path] +)], cmdclass={'build_ext': Build, 'sdist': PackSource}) From 7774cc6e843882a0c9477b96583229cd7ba6076e Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Thu, 2 Dec 2021 12:11:42 +0800 Subject: [PATCH 085/118] Bump version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 7483fec36..ea0e99fcc 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.3.post1" +__version__ = "3.0.4" from enum import IntEnum, auto from .slvs import ( From dd13e3a222b9d22b2a18f1c4e076f26159855fe8 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Thu, 2 Dec 2021 23:47:25 +0800 Subject: [PATCH 086/118] Add workflow for Python. --- .github/workflows/python.yml | 66 ++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/python.yml diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 000000000..273088594 --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,66 @@ +name: Python + +on: + push: + branches: [ python ] + tags: [ py* ] + +jobs: + python-build: + strategy: + matrix: + pyver: + - "3.6" + - "3.7" + - "3.8" + - "3.9" + - "3.10" + os: + - ubuntu-latest + - macos-latest + - windows-latest + runs-on: ${{ matrix.os }} + env: + TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} + TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} + steps: + - uses: actions/checkout@v2 + - run: git submodule update --init extlib/mimalloc + - uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.pyver }} + - name: Build and test + working-directory: cython + run: | + python -m pip install -U pip setuptools wheel build + python -m pip install -e . + python -m unittest + - name: Pack + working-directory: cython + run: python -m build + - if: matrix.os == 'ubuntu-latest' + name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.os }}-py${{ matrix.pyver }} + path: cython/dist/*.tar.gz + - if: matrix.os != 'ubuntu-latest' + name: Upload artifact + uses: actions/upload-artifact@v2 + with: + name: ${{ matrix.os }}-py${{ matrix.pyver }} + path: cython/dist/*.whl + - if: startsWith(github.ref, 'refs/tags/py') + run: python -m pip install twine + - if: startsWith(github.ref, 'refs/tags/py') && matrix.os == 'ubuntu-latest' + name: release + working-directory: cython + run: | + python -m build --sdist + python -m twine upload "dist/*.tar.gz" --skip-existing + - if: startsWith(github.ref, 'refs/tags/py') && matrix.os != 'ubuntu-latest' + name: release + working-directory: cython + run: | + python -m build --wheel + python -m twine upload "dist/*.whl" --skip-existing From 84f30699ddffb56e60591a35e4b0cede01baebd5 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 3 Dec 2021 12:18:40 +0800 Subject: [PATCH 087/118] Remove old CI badges. --- cython/README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/cython/README.md b/cython/README.md index 1c0454ecf..030f4222a 100644 --- a/cython/README.md +++ b/cython/README.md @@ -1,5 +1,3 @@ -[![Build status](https://ci.appveyor.com/api/projects/status/b2o8jw7xnfqghqr5?svg=true)](https://ci.appveyor.com/project/KmolYuan/solvespace) -[![Build status](https://img.shields.io/travis/KmolYuan/solvespace.svg?logo=travis)](https://travis-ci.org/KmolYuan/solvespace) [![PyPI](https://img.shields.io/pypi/v/python-solvespace.svg)](https://pypi.org/project/python-solvespace/) [![GitHub license](https://img.shields.io/badge/license-GPLv3+-blue.svg)](https://raw.githubusercontent.com/KmolYuan/solvespace/master/LICENSE) From ba07e95c0e1703847639e20e2ee37d524adba9d9 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 3 Dec 2021 18:04:19 +0800 Subject: [PATCH 088/118] Simplify CI scripts. --- .github/workflows/python.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 273088594..a570fed85 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -55,12 +55,8 @@ jobs: - if: startsWith(github.ref, 'refs/tags/py') && matrix.os == 'ubuntu-latest' name: release working-directory: cython - run: | - python -m build --sdist - python -m twine upload "dist/*.tar.gz" --skip-existing + run: python -m twine upload "dist/*.tar.gz" --skip-existing - if: startsWith(github.ref, 'refs/tags/py') && matrix.os != 'ubuntu-latest' name: release working-directory: cython - run: | - python -m build --wheel - python -m twine upload "dist/*.whl" --skip-existing + run: python -m twine upload "dist/*.whl" --skip-existing From 3c98d4d2700ac0b01550b51282e3bf8defa1d1a8 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 11 Dec 2021 17:46:28 +0800 Subject: [PATCH 089/118] Bump version. --- cython/python_solvespace/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index ea0e99fcc..ec8608374 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.4" +__version__ = "3.0.5" from enum import IntEnum, auto from .slvs import ( From 64fb12719561c4ecc8454e06dcd8229924cf07a5 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 11 Dec 2021 18:49:15 +0800 Subject: [PATCH 090/118] Make solver copyable and implement its serialization. --- cython/python_solvespace/slvs.pxd | 7 +++- cython/python_solvespace/slvs.pyi | 12 ++++++ cython/python_solvespace/slvs.pyx | 68 ++++++++++++++++++++----------- cython/test/test_slvs.py | 8 +++- 4 files changed, 69 insertions(+), 26 deletions(-) diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index 05cd83dff..dd14ab8af 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -247,7 +247,7 @@ cdef class SolverSystem: 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 collect_failed(self) nogil cdef void free(self) cpdef void set_group(self, size_t g) cpdef int group(self) @@ -257,6 +257,11 @@ cdef class SolverSystem: cpdef object constraints(self) cpdef list failures(self) cpdef int solve(self) + + cpdef size_t param_len(self) + cpdef size_t entity_len(self) + cpdef size_t cons_len(self) + cpdef Entity create_2d_base(self) cdef Slvs_hParam new_param(self, double val) nogil cdef Slvs_hEntity eh(self) nogil diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index a8501fa18..47f40c2bc 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -99,6 +99,9 @@ class SolverSystem: """Initialization method. Create a solver system.""" ... + def copy(self) -> SolverSystem: + ... + def clear(self) -> None: ... @@ -126,6 +129,15 @@ class SolverSystem: def solve(self) -> int: ... + def param_len(self) -> int: + ... + + def entity_len(self) -> int: + ... + + def cons_len(self) -> int: + ... + def create_2d_base(self) -> Entity: ... diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 931726bee..ace999627 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -279,13 +279,26 @@ cdef class SolverSystem: class. """ - def __cinit__(self): - self.g = 0 + def __cinit__(self, int g = 0, object param_list=None, object entity_list=None, object cons_list=None): + self.g = g self.sys.params = self.sys.entities = self.sys.constraints = 0 + if param_list is not None: + self.param_list = param_list + if entity_list is not None: + self.entity_list = entity_list + if cons_list is not None: + self.cons_list = cons_list def __dealloc__(self): self.free() + def __reduce__(self): + return (self.__class__, (self.g, self.param_list, self.entity_list, self.cons_list)) + + def copy(self): + """Copy the solver.""" + return SolverSystem(self.g, self.param_list, self.entity_list, self.cons_list) + cdef inline void copy_to_sys(self) nogil: """Copy data from stack into system.""" cdef int i = 0 @@ -293,13 +306,11 @@ cdef class SolverSystem: 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: @@ -328,8 +339,9 @@ cdef class SolverSystem: self.failed_list.clear() self.free() - cdef inline void failed_collecting(self) nogil: - """Collecting the failed constraints.""" + cdef inline void collect_failed(self) nogil: + """Collect the failed constraints.""" + self.failed_list.clear() cdef int i for i in range(self.sys.faileds): self.failed_list.push_back(self.sys.failed[i]) @@ -408,11 +420,14 @@ cdef class SolverSystem: """Start the solving, return the result flag.""" # Parameters self.sys.param = PyMem_Malloc(self.param_list.size() * sizeof(Slvs_Param)) + self.sys.params = self.param_list.size() # Entities self.sys.entity = PyMem_Malloc(self.entity_list.size() * sizeof(Slvs_Entity)) + self.sys.entities = self.entity_list.size() # Constraints cdef size_t cons_size = self.cons_list.size() self.sys.constraint = PyMem_Malloc(cons_size * sizeof(Slvs_Constraint)) + self.sys.constraints = self.cons_list.size() self.sys.failed = PyMem_Malloc(cons_size * sizeof(Slvs_hConstraint)) self.sys.faileds = cons_size @@ -422,10 +437,22 @@ cdef class SolverSystem: Slvs_Solve(&self.sys, self.g) # Failed constraints and free memory. self.copy_from_sys() - self.failed_collecting() + self.collect_failed() self.free() return self.sys.result + cpdef size_t param_len(self): + """The length of parameter list.""" + return self.param_list.size() + + cpdef size_t entity_len(self): + """The length of parameter list.""" + return self.entity_list.size() + + cpdef size_t cons_len(self): + """The length of parameter list.""" + return self.cons_list.size() + cpdef Entity create_2d_base(self): """Create a 2D system on current group, return the handle of work plane. @@ -437,15 +464,13 @@ cdef class SolverSystem: cdef inline Slvs_hParam new_param(self, double val) nogil: """Add a parameter.""" - self.sys.params += 1 - cdef Slvs_hParam h = self.sys.params + cdef Slvs_hParam h = self.param_list.size() + 1 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 + return self.entity_list.size() + 1 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. @@ -458,10 +483,8 @@ cdef class SolverSystem: 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) + self.entity_list.push_back(Slvs_MakePoint2d(self.eh(), self.g, wp.h, u_p, v_p)) + return Entity.create(&self.entity_list.back(), 2) cpdef Entity add_point_3d(self, double x, double y, double z): """Add a 3D point then return the handle. @@ -471,10 +494,8 @@ cdef class SolverSystem: 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) + self.entity_list.push_back(Slvs_MakePoint3d(self.eh(), self.g, x_p, y_p, z_p)) + return Entity.create(&self.entity_list.back(), 3) cpdef Entity add_normal_2d(self, Entity wp): """Add a 2D normal orthogonal to specific work plane (`wp`) @@ -482,9 +503,9 @@ cdef class SolverSystem: """ 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) + + self.entity_list.push_back(Slvs_MakeNormal2d(self.eh(), self.g, wp.h)) + return Entity.create(&self.entity_list.back(), 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. @@ -651,9 +672,8 @@ cdef class SolverSystem: 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.h = self.cons_list.size() + 1 c.group = self.g c.type = c_type c.wrkpl = wp.h diff --git a/cython/test/test_slvs.py b/cython/test/test_slvs.py index a491db5da..3fd910a29 100644 --- a/cython/test/test_slvs.py +++ b/cython/test/test_slvs.py @@ -34,12 +34,18 @@ def test_crank_rocker(self): 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) + # Solver copy test + sys_new = sys.copy() + result_flag = sys_new.solve() + self.assertEqual(result_flag, ResultFlag.OKAY) + x, y = sys_new.params(p2.params) + self.assertAlmostEqual(39.54852, x, 4) + self.assertAlmostEqual(61.91009, y, 4) def test_involute(self): """Involute example.""" From b7d192129dd920de9a2c06471d2b9bec52636f61 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 11 Dec 2021 19:51:55 +0800 Subject: [PATCH 091/118] Update stubs. --- cython/python_solvespace/slvs.pyi | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 47f40c2bc..5236f815d 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -95,8 +95,14 @@ class Entity: class SolverSystem: - def __init__(self) -> None: - """Initialization method. Create a solver system.""" + def __init__(self, g: int = 0, param_list=None, entity_list=None, cons_list=None) -> None: + """Create a solver system. + + The current group, parameters, entities, constraints can be set from an existing solver. + """ + ... + + def __reduce__(self): ... def copy(self) -> SolverSystem: From 35fed04b631c80db906226c017b770ba51df56bd Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 11 Dec 2021 20:26:36 +0800 Subject: [PATCH 092/118] Change the type of parameter list. --- cython/python_solvespace/slvs.pxd | 3 +-- cython/python_solvespace/slvs.pyx | 13 ++++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index dd14ab8af..935f0bdd9 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -11,7 +11,6 @@ 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: @@ -239,7 +238,7 @@ cdef class SolverSystem: cdef Slvs_hGroup g cdef Slvs_System sys - cdef cmap[Slvs_hParam, Slvs_Param] param_list + cdef vector[Slvs_Param] param_list cdef vector[Slvs_Entity] entity_list cdef vector[Slvs_Constraint] cons_list cdef vector[Slvs_hConstraint] failed_list diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index ace999627..d628ce8db 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -11,7 +11,6 @@ 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 @@ -302,9 +301,9 @@ cdef class SolverSystem: 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 + cdef Slvs_Param param for param in self.param_list: - self.sys.param[i] = param.second + self.sys.param[i] = param i += 1 i = 0 cdef Slvs_Entity entity @@ -324,7 +323,7 @@ cdef class SolverSystem: 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] + self.param_list.push_back(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): @@ -378,7 +377,7 @@ cdef class SolverSystem: i = 0 cdef Slvs_hParam h for h in p.param_list: - self.param_list[h].val = params[i] + self.param_list[h - 1].val = params[i] i += 1 cpdef tuple params(self, Params p): @@ -389,7 +388,7 @@ cdef class SolverSystem: param_list = [] cdef Slvs_hParam h for h in p.param_list: - param_list.append(self.param_list[h].val) + param_list.append(self.param_list[h - 1].val) return tuple(param_list) cpdef int dof(self): @@ -465,7 +464,7 @@ cdef class SolverSystem: cdef inline Slvs_hParam new_param(self, double val) nogil: """Add a parameter.""" cdef Slvs_hParam h = self.param_list.size() + 1 - self.param_list[h] = Slvs_MakeParam(h, self.g, val) + self.param_list.push_back(Slvs_MakeParam(h, self.g, val)) return h cdef inline Slvs_hEntity eh(self) nogil: From cba265d3405dbf36283ebaab1a669e7c73ba98a8 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 11 Dec 2021 20:53:13 +0800 Subject: [PATCH 093/118] Codebase improvement. --- cython/python_solvespace/slvs.pxd | 7 +- cython/python_solvespace/slvs.pyi | 2 +- cython/python_solvespace/slvs.pyx | 102 ++++++------------------------ 3 files changed, 21 insertions(+), 90 deletions(-) diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index 935f0bdd9..b3190bcdc 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -243,15 +243,12 @@ cdef class SolverSystem: 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 SolverSystem copy(self) cpdef void clear(self) - cdef void collect_failed(self) nogil - cdef void free(self) cpdef void set_group(self, size_t g) cpdef int group(self) cpdef void set_params(self, Params p, object params) - cpdef tuple params(self, Params p) + cpdef list params(self, Params p) cpdef int dof(self) cpdef object constraints(self) cpdef list failures(self) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 5236f815d..393b6e189 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -120,7 +120,7 @@ class SolverSystem: def set_params(self, p: Params, params: Sequence[float]) -> None: ... - def params(self, p: Params) -> Tuple[float, ...]: + def params(self, p: Params) -> List[float]: ... def dof(self) -> int: diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index d628ce8db..eb733a9da 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -9,7 +9,6 @@ license: GPLv3+ email: pyslvs@gmail.com """ -from cpython.mem cimport PyMem_Malloc, PyMem_Free from cpython.object cimport Py_EQ, Py_NE from collections import Counter @@ -288,47 +287,13 @@ cdef class SolverSystem: if cons_list is not None: self.cons_list = cons_list - def __dealloc__(self): - self.free() - def __reduce__(self): return (self.__class__, (self.g, self.param_list, self.entity_list, self.cons_list)) - def copy(self): + cpdef SolverSystem copy(self): """Copy the solver.""" return SolverSystem(self.g, self.param_list, self.entity_list, self.cons_list) - cdef inline void copy_to_sys(self) nogil: - """Copy data from stack into system.""" - cdef int i = 0 - cdef Slvs_Param param - for param in self.param_list: - self.sys.param[i] = param - 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.push_back(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 @@ -336,51 +301,30 @@ cdef class SolverSystem: self.entity_list.clear() self.cons_list.clear() self.failed_list.clear() - self.free() - - cdef inline void collect_failed(self) nogil: - """Collect the failed constraints.""" - self.failed_list.clear() - 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 + self.g = g cpdef int group(self): """Return the current group.""" - return self.g + return self.g cpdef void set_params(self, Params p, object params): - """Set the parameters from a [Params] handle (`p`) belong to this - system. + """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}") + params = list(params) + if p.param_list.size() != len(params): + raise ValueError(f"number of parameters {len(params)} are not match {p.param_list.size()}") - i = 0 + cdef int i = 0 cdef Slvs_hParam h for h in p.param_list: self.param_list[h - 1].val = params[i] i += 1 - cpdef tuple params(self, Params p): + cpdef list params(self, Params p): """Get the parameters from a [Params] handle (`p`) belong to this system. The length of tuple is decided by handle. @@ -389,7 +333,7 @@ cdef class SolverSystem: cdef Slvs_hParam h for h in p.param_list: param_list.append(self.param_list[h - 1].val) - return tuple(param_list) + return param_list cpdef int dof(self): """Return the degrees of freedom of current group. @@ -409,35 +353,25 @@ cdef class SolverSystem: 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 + return self.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)) + self.sys.param = self.param_list.data() self.sys.params = self.param_list.size() # Entities - self.sys.entity = PyMem_Malloc(self.entity_list.size() * sizeof(Slvs_Entity)) + self.sys.entity = self.entity_list.data() self.sys.entities = self.entity_list.size() # Constraints - cdef size_t cons_size = self.cons_list.size() - self.sys.constraint = PyMem_Malloc(cons_size * sizeof(Slvs_Constraint)) + self.sys.constraint = self.cons_list.data() self.sys.constraints = self.cons_list.size() - self.sys.failed = PyMem_Malloc(cons_size * sizeof(Slvs_hConstraint)) - self.sys.faileds = cons_size - - # Copy to system - self.copy_to_sys() + # Faileds + self.failed_list.reserve(self.cons_list.size()) + self.sys.failed = self.failed_list.data() + self.sys.faileds = self.cons_list.size() # Solve Slvs_Solve(&self.sys, self.g) - # Failed constraints and free memory. - self.copy_from_sys() - self.collect_failed() - self.free() return self.sys.result cpdef size_t param_len(self): From 94e1db6ab23c8d265d9007af37a49b12359d7404 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sat, 11 Dec 2021 21:06:33 +0800 Subject: [PATCH 094/118] Adjust unit test. --- cython/test/test_slvs.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cython/test/test_slvs.py b/cython/test/test_slvs.py index 3fd910a29..cfd05d152 100644 --- a/cython/test/test_slvs.py +++ b/cython/test/test_slvs.py @@ -29,18 +29,19 @@ def test_crank_rocker(self): 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) + + sys_new = sys.copy() # solver copy test + 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) - # Solver copy test - sys_new = sys.copy() + result_flag = sys_new.solve() self.assertEqual(result_flag, ResultFlag.OKAY) x, y = sys_new.params(p2.params) From bd98749195c230730212b5383a5d362c7fae3416 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 12 Dec 2021 10:51:01 +0800 Subject: [PATCH 095/118] Remove the dangling pointer. --- cython/python_solvespace/slvs.pxd | 2 +- cython/python_solvespace/slvs.pyx | 28 +++++++++++++++------------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index b3190bcdc..4d82d844c 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -236,8 +236,8 @@ cdef class Entity: cdef class SolverSystem: + cdef int dof_v cdef Slvs_hGroup g - cdef Slvs_System sys cdef vector[Slvs_Param] param_list cdef vector[Slvs_Entity] entity_list cdef vector[Slvs_Constraint] cons_list diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index eb733a9da..76f5cc865 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -279,7 +279,6 @@ cdef class SolverSystem: def __cinit__(self, int g = 0, object param_list=None, object entity_list=None, object cons_list=None): self.g = g - self.sys.params = self.sys.entities = self.sys.constraints = 0 if param_list is not None: self.param_list = param_list if entity_list is not None: @@ -296,6 +295,7 @@ cdef class SolverSystem: cpdef void clear(self): """Clear the system.""" + self.dof_v = 0 self.g = 0 self.param_list.clear() self.entity_list.clear() @@ -337,9 +337,9 @@ cdef class SolverSystem: cpdef int dof(self): """Return the degrees of freedom of current group. - Only can be called after solving. + Only can be called after solved. """ - return self.sys.dof + return self.dof_v cpdef object constraints(self): """Return the number of each constraint type. @@ -357,22 +357,24 @@ cdef class SolverSystem: cpdef int solve(self): """Start the solving, return the result flag.""" + cdef Slvs_System sys # Parameters - self.sys.param = self.param_list.data() - self.sys.params = self.param_list.size() + sys.param = self.param_list.data() + sys.params = self.param_list.size() # Entities - self.sys.entity = self.entity_list.data() - self.sys.entities = self.entity_list.size() + sys.entity = self.entity_list.data() + sys.entities = self.entity_list.size() # Constraints - self.sys.constraint = self.cons_list.data() - self.sys.constraints = self.cons_list.size() + sys.constraint = self.cons_list.data() + sys.constraints = self.cons_list.size() # Faileds self.failed_list.reserve(self.cons_list.size()) - self.sys.failed = self.failed_list.data() - self.sys.faileds = self.cons_list.size() + sys.failed = self.failed_list.data() + sys.faileds = self.cons_list.size() # Solve - Slvs_Solve(&self.sys, self.g) - return self.sys.result + Slvs_Solve(&sys, self.g) + self.dof_v = sys.dof + return sys.result cpdef size_t param_len(self): """The length of parameter list.""" From c28f124e9aa8bdbf21290998e489fcefd0e8e5c3 Mon Sep 17 00:00:00 2001 From: Yuan Chang Date: Sun, 12 Dec 2021 12:37:05 +0800 Subject: [PATCH 096/118] Update readme. --- cython/README.md | 59 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/cython/README.md b/cython/README.md index 030f4222a..8af0c053b 100644 --- a/cython/README.md +++ b/cython/README.md @@ -8,6 +8,45 @@ Python library from the solver of SolveSpace, an open source CAD software. + [Python API](https://pyslvs-ui.readthedocs.io/en/stable/python-solvespace-api/) + [C API](https://github.com/solvespace/solvespace/blob/master/exposed/DOC.txt) +The example extracted from unit test: + +```python +from python_solvespace import SolverSystem, ResultFlag + +sys = SolverSystem() +wp = sys.create_2d_base() # Workplane (Entity) +p0 = sys.add_point_2d(0, 0, wp) # Entity +sys.dragged(p0, wp) # Make a constraint with the entity +... +line0 = sys.add_line_2d(p0, p1, wp) # Create entity with others +... +line1 = sys.add_line_2d(p0, p3, wp) +sys.angle(line0, line1, 45, wp) # Constrain two entities +if sys.solve() == ResultFlag.OKAY: + # Get the result (unpack from the entity or parameters) + # x and y are actually float type + dof = sys.dof() + x, y = sys.params(p2.params) + ... +else: + # Error! + # Get the list of all constraints + failures = sys.failures() + ... +``` + +Solver can also be serialized and copied. + +```python +import pickle +print(pickle.dumps(sys)) + +sys_new = sys.copy() +# equivalent to +func, args = sys.__reduce__() +sys_new = func(*args) +``` + # Install ```bash @@ -16,19 +55,24 @@ pip install python-solvespace # Build and Test (Repository) -Build and install the module: +First build and install the module from the repo: ```bash -pip install python-solvespace -# From repository -git submodule update --init +git submodule update --init extlib/mimalloc +cd cython pip install -e . ``` -Run unit tests: +Build the module: ```bash -python test +pip install -e . --no-deps +``` + +Run the unit tests: + +```bash +python -m unittest ``` Uninstall the module: @@ -36,6 +80,3 @@ 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/ From 613545eba5825852eee45b595bc9e914494404d5 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 12 Dec 2021 14:15:06 +0800 Subject: [PATCH 097/118] Add a method to generate entity handle. --- cython/README.md | 4 +++ cython/python_solvespace/slvs.pxd | 2 +- cython/python_solvespace/slvs.pyi | 3 ++ cython/python_solvespace/slvs.pyx | 59 ++++++++++++++++++++++--------- 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/cython/README.md b/cython/README.md index 8af0c053b..813f1129d 100644 --- a/cython/README.md +++ b/cython/README.md @@ -22,6 +22,8 @@ line0 = sys.add_line_2d(p0, p1, wp) # Create entity with others ... line1 = sys.add_line_2d(p0, p3, wp) sys.angle(line0, line1, 45, wp) # Constrain two entities +line1 = sys.entity(-1) # Entity handle can be re-generated and negatively indexed +... if sys.solve() == ResultFlag.OKAY: # Get the result (unpack from the entity or parameters) # x and y are actually float type @@ -47,6 +49,8 @@ func, args = sys.__reduce__() sys_new = func(*args) ``` +The entity and parameter handles should have the same lifetime to the solver. + # Install ```bash diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index 4d82d844c..430d5364b 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -214,7 +214,7 @@ cdef class Entity: cdef readonly Params params @staticmethod - cdef Entity create(Slvs_Entity *e, size_t p_size) + cdef Entity create(Slvs_Entity *e) cpdef bint is_3d(self) cpdef bint is_none(self) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 393b6e189..11a058c9c 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -105,6 +105,9 @@ class SolverSystem: def __reduce__(self): ... + def entity(self, i: int) -> Entity: + ... + def copy(self) -> SolverSystem: ... diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 76f5cc865..52366364b 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -160,13 +160,16 @@ _NAME_OF_CONSTRAINTS = { cdef class Entity: - """The handles of entities.""" + """The handles of entities. + + This handle **should** be dropped after system removed. + """ FREE_IN_3D = _E_FREE_IN_3D NONE = _E_NONE @staticmethod - cdef Entity create(Slvs_Entity *e, size_t p_size): + cdef Entity create(Slvs_Entity *e): """Constructor.""" cdef Entity entity = Entity.__new__(Entity) with nogil: @@ -174,6 +177,17 @@ cdef class Entity: entity.h = e.h entity.wp = e.wrkpl entity.g = e.group + cdef size_t p_size + if e.type == SLVS_E_DISTANCE: + p_size = 1 + elif e.type == SLVS_E_POINT_IN_2D: + p_size = 2 + elif e.type == SLVS_E_POINT_IN_3D: + p_size = 3 + elif e.type == SLVS_E_NORMAL_IN_3D: + p_size = 4 + else: + p_size = 0 entity.params = Params.create(e.param, p_size) return entity @@ -273,11 +287,10 @@ cdef class SolverSystem: """A solver system of Python-Solvespace. - The operation of entities and constraints are using the methods of this - class. + The operation of entities and constraints are using the methods of this class. """ - def __cinit__(self, int g = 0, object param_list=None, object entity_list=None, object cons_list=None): + def __cinit__(self, int g = 0, param_list=None, entity_list=None, cons_list=None): self.g = g if param_list is not None: self.param_list = param_list @@ -289,9 +302,21 @@ cdef class SolverSystem: def __reduce__(self): return (self.__class__, (self.g, self.param_list, self.entity_list, self.cons_list)) + def entity(self, int i) -> Entity: + """Generate entity handle, it can only be used with this system. + + Negative index is allowed. + """ + if i < 0: + return Entity.create(&self.entity_list[self.entity_list.size() + i]) + elif i >= self.entity_list.size(): + raise IndexError(f"index {i} is out of bound {self.entity_list.size()}") + else: + return Entity.create(&self.entity_list[i]) + cpdef SolverSystem copy(self): """Copy the solver.""" - return SolverSystem(self.g, self.param_list, self.entity_list, self.cons_list) + return SolverSystem.__new__(SolverSystem, self.g, self.param_list, self.entity_list, self.cons_list) cpdef void clear(self): """Clear the system.""" @@ -419,7 +444,7 @@ cdef class SolverSystem: cdef Slvs_hParam u_p = self.new_param(u) cdef Slvs_hParam v_p = self.new_param(v) self.entity_list.push_back(Slvs_MakePoint2d(self.eh(), self.g, wp.h, u_p, v_p)) - return Entity.create(&self.entity_list.back(), 2) + return Entity.create(&self.entity_list.back()) cpdef Entity add_point_3d(self, double x, double y, double z): """Add a 3D point then return the handle. @@ -430,7 +455,7 @@ cdef class SolverSystem: cdef Slvs_hParam y_p = self.new_param(y) cdef Slvs_hParam z_p = self.new_param(z) self.entity_list.push_back(Slvs_MakePoint3d(self.eh(), self.g, x_p, y_p, z_p)) - return Entity.create(&self.entity_list.back(), 3) + return Entity.create(&self.entity_list.back()) cpdef Entity add_normal_2d(self, Entity wp): """Add a 2D normal orthogonal to specific work plane (`wp`) @@ -440,7 +465,7 @@ cdef class SolverSystem: raise TypeError(f"{wp} is not a work plane") self.entity_list.push_back(Slvs_MakeNormal2d(self.eh(), self.g, wp.h)) - return Entity.create(&self.entity_list.back(), 0) + return Entity.create(&self.entity_list.back()) cpdef Entity add_normal_3d(self, double qw, double qx, double qy, double qz): """Add a 3D normal from quaternion then return the handle. @@ -454,7 +479,7 @@ cdef class SolverSystem: 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) + return Entity.create(&self.entity_list.back()) cpdef Entity add_distance(self, double d, Entity wp): """Add a distance to specific work plane (`wp`) then return the handle. @@ -467,7 +492,7 @@ cdef class SolverSystem: 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) + return Entity.create(&self.entity_list.back()) 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. @@ -483,7 +508,7 @@ cdef class SolverSystem: 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) + return Entity.create(&self.entity_list.back()) cpdef Entity add_line_3d(self, Entity p1, Entity p2): """Add a 3D line then return the handle. @@ -497,7 +522,7 @@ cdef class SolverSystem: 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) + return Entity.create(&self.entity_list.back()) 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 @@ -518,7 +543,7 @@ cdef class SolverSystem: 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) + return Entity.create(&self.entity_list.back()) 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. @@ -538,7 +563,7 @@ cdef class SolverSystem: 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) + return Entity.create(&self.entity_list.back()) 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. @@ -558,7 +583,7 @@ cdef class SolverSystem: 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) + return Entity.create(&self.entity_list.back()) cpdef Entity add_work_plane(self, Entity origin, Entity nm): """Add a work plane then return the handle. @@ -572,7 +597,7 @@ cdef class SolverSystem: 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) + return Entity.create(&self.entity_list.back()) cpdef void add_constraint( self, From d9b3acc218fd9881d26376383314d28d2967b0df Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 12 Dec 2021 14:21:12 +0800 Subject: [PATCH 098/118] Update readme. --- cython/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/README.md b/cython/README.md index 813f1129d..490a96755 100644 --- a/cython/README.md +++ b/cython/README.md @@ -37,7 +37,7 @@ else: ... ``` -Solver can also be serialized and copied. +Solver can also be serialized and copied, but can not modify or undo last step. ```python import pickle From 3d498223f5aa7a327601d515646be207ad70d39b Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Sun, 12 Dec 2021 14:58:10 +0800 Subject: [PATCH 099/118] Add missing docstring. --- cython/python_solvespace/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index ec8608374..6ded1338b 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -35,6 +35,7 @@ class Constraint(IntEnum): + """Symbol of the constraint types.""" # Expose macro of constraint types POINTS_COINCIDENT = 100000 PT_PT_DISTANCE = auto() @@ -73,6 +74,7 @@ class Constraint(IntEnum): class ResultFlag(IntEnum): + """Symbol of the result flags.""" # Expose macro of result flags OKAY = 0 INCONSISTENT = auto() From a4652f0324fe955564942ce4745817c71e71b21d Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 14 Dec 2021 09:37:51 +0800 Subject: [PATCH 100/118] Hide copy API. --- cython/README.md | 3 --- cython/python_solvespace/slvs.pyi | 7 ++----- cython/python_solvespace/slvs.pyx | 24 ++++++++++++++---------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/cython/README.md b/cython/README.md index 490a96755..b81552468 100644 --- a/cython/README.md +++ b/cython/README.md @@ -44,9 +44,6 @@ import pickle print(pickle.dumps(sys)) sys_new = sys.copy() -# equivalent to -func, args = sys.__reduce__() -sys_new = func(*args) ``` The entity and parameter handles should have the same lifetime to the solver. diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 11a058c9c..8c2f05f8c 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -95,11 +95,8 @@ class Entity: class SolverSystem: - def __init__(self, g: int = 0, param_list=None, entity_list=None, cons_list=None) -> None: - """Create a solver system. - - The current group, parameters, entities, constraints can be set from an existing solver. - """ + def __init__(self) -> None: + """Create a solver system.""" ... def __reduce__(self): diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 52366364b..72cbf3ee4 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -13,6 +13,16 @@ from cpython.object cimport Py_EQ, Py_NE from collections import Counter +def _create_sys(dof_v, g, param_list, entity_list, cons_list): + cdef SolverSystem s = SolverSystem.__new__(SolverSystem) + s.dof_v = dof_v + s.g = g + s.param_list = param_list + s.entity_list = entity_list + s.cons_list = cons_list + return s + + cpdef tuple quaternion_u(double qw, double qx, double qy, double qz): """Input quaternion, return unit vector of U axis. @@ -290,17 +300,11 @@ cdef class SolverSystem: The operation of entities and constraints are using the methods of this class. """ - def __cinit__(self, int g = 0, param_list=None, entity_list=None, cons_list=None): - self.g = g - if param_list is not None: - self.param_list = param_list - if entity_list is not None: - self.entity_list = entity_list - if cons_list is not None: - self.cons_list = cons_list + def __cinit__(self): + self.g = 0 def __reduce__(self): - return (self.__class__, (self.g, self.param_list, self.entity_list, self.cons_list)) + return (_create_sys, (self.dof_v, self.g, self.param_list, self.entity_list, self.cons_list)) def entity(self, int i) -> Entity: """Generate entity handle, it can only be used with this system. @@ -316,7 +320,7 @@ cdef class SolverSystem: cpdef SolverSystem copy(self): """Copy the solver.""" - return SolverSystem.__new__(SolverSystem, self.g, self.param_list, self.entity_list, self.cons_list) + return _create_sys(self.dof_v, self.g, self.param_list, self.entity_list, self.cons_list) cpdef void clear(self): """Clear the system.""" From 97f5ee928a622e3adde086e16ce7d178a6964ba5 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Mon, 20 Dec 2021 17:10:21 +0800 Subject: [PATCH 101/118] Bump version and update Python support. --- cython/python_solvespace/__init__.py | 2 +- cython/setup.cfg | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 6ded1338b..7a4185786 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.5" +__version__ = "3.0.6" from enum import IntEnum, auto from .slvs import ( diff --git a/cython/setup.cfg b/cython/setup.cfg index 06d6d07fb..c4b757d22 100644 --- a/cython/setup.cfg +++ b/cython/setup.cfg @@ -14,6 +14,7 @@ classifiers = Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 Programming Language :: Cython Topic :: Scientific/Engineering License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) From 77ccdfacff4052403eef0f7ff349fad92486b401 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 8 Feb 2022 14:11:51 +0800 Subject: [PATCH 102/118] Fix missing failures. --- cython/python_solvespace/slvs.pyx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 72cbf3ee4..1cfa1f005 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -397,11 +397,12 @@ cdef class SolverSystem: sys.constraint = self.cons_list.data() sys.constraints = self.cons_list.size() # Faileds - self.failed_list.reserve(self.cons_list.size()) + self.failed_list = vector[Slvs_hConstraint](self.cons_list.size(), 0) sys.failed = self.failed_list.data() - sys.faileds = self.cons_list.size() + sys.faileds = self.failed_list.size() # Solve Slvs_Solve(&sys, self.g) + self.failed_list.resize(sys.faileds) self.dof_v = sys.dof return sys.result From da5cd070ab5d0231e4780bce66066f12aaa4921c Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 8 Feb 2022 14:40:44 +0800 Subject: [PATCH 103/118] Wrap solve result. --- cython/python_solvespace/__init__.py | 49 ------------------------- cython/python_solvespace/slvs.pxd | 2 +- cython/python_solvespace/slvs.pyx | 54 +++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 51 deletions(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 7a4185786..b2058fda7 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -10,7 +10,6 @@ __email__ = "pyslvs@gmail.com" __version__ = "3.0.6" -from enum import IntEnum, auto from .slvs import ( quaternion_u, quaternion_v, @@ -32,51 +31,3 @@ 'Entity', 'SolverSystem', ] - - -class Constraint(IntEnum): - """Symbol of the constraint types.""" - # 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): - """Symbol of the result flags.""" - # Expose macro of result flags - OKAY = 0 - INCONSISTENT = auto() - DIDNT_CONVERGE = auto() - TOO_MANY_UNKNOWNS = auto() diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index 430d5364b..0394befff 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -252,7 +252,7 @@ cdef class SolverSystem: cpdef int dof(self) cpdef object constraints(self) cpdef list failures(self) - cpdef int solve(self) + cdef int solve_c(self) nogil cpdef size_t param_len(self) cpdef size_t entity_len(self) diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 1cfa1f005..55db88ba7 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -10,6 +10,7 @@ email: pyslvs@gmail.com """ from cpython.object cimport Py_EQ, Py_NE +from enum import IntEnum, auto from collections import Counter @@ -65,6 +66,54 @@ cpdef tuple make_quaternion(double ux, double uy, double uz, double vx, double v return qw, qx, qy, qz +class Constraint(IntEnum): + """Symbol of the constraint types.""" + # 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): + """Symbol of the result flags.""" + # Expose macro of result flags + OKAY = 0 + INCONSISTENT = auto() + DIDNT_CONVERGE = auto() + TOO_MANY_UNKNOWNS = auto() + + cdef class Params: """Python object to handle multiple parameter handles.""" @@ -384,7 +433,7 @@ cdef class SolverSystem: """Return a list of failed constraint numbers.""" return self.failed_list - cpdef int solve(self): + cdef int solve_c(self) nogil: """Start the solving, return the result flag.""" cdef Slvs_System sys # Parameters @@ -406,6 +455,9 @@ cdef class SolverSystem: self.dof_v = sys.dof return sys.result + def solve(self): + return ResultFlag(self.solve_c()) + cpdef size_t param_len(self): """The length of parameter list.""" return self.param_list.size() From 30b7980d4e8f63aaa142fadbd6fef0433949fd5b Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 8 Feb 2022 15:22:37 +0800 Subject: [PATCH 104/118] Support eigen. --- .github/workflows/python.yml | 2 +- cython/setup.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index a570fed85..75b30b0f7 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -25,7 +25,7 @@ jobs: TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} steps: - uses: actions/checkout@v2 - - run: git submodule update --init extlib/mimalloc + - run: git submodule update --init extlib/mimalloc extlib/eigen - uses: actions/setup-python@v2 with: python-version: ${{ matrix.pyver }} diff --git a/cython/setup.py b/cython/setup.py index 9cddb0b68..a82af6b0b 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -24,6 +24,7 @@ mimalloc_path = join(extlib_path, 'mimalloc') mimalloc_include_path = join(mimalloc_path, 'include') mimalloc_src_path = join(mimalloc_path, 'src') +eigen_path = join(include_path, 'Eigen') build_dir = 'build' macros = [ ('M_PI', 'PI'), @@ -97,6 +98,7 @@ def copy_source(dry_run): dir_util.copy_tree(join('..', 'include'), include_path, dry_run=dry_run) + dir_util.copy_tree(join('..', 'extlib', 'eigen', 'Eigen'), eigen_path, dry_run=dry_run) dir_util.copy_tree(join('..', 'extlib', 'mimalloc', 'include'), mimalloc_include_path, dry_run=dry_run) From 5faa2e6bf771a824895614bf95372dc07ec50aab Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 8 Feb 2022 15:25:35 +0800 Subject: [PATCH 105/118] Fix re-exports. --- cython/python_solvespace/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index b2058fda7..46aa07f98 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -15,6 +15,8 @@ quaternion_v, quaternion_n, make_quaternion, + Constraint, + ResultFlag, Params, Entity, SolverSystem, From bf939135a424d6ae4a09bf057460b48b18c21e62 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 9 Feb 2022 08:30:35 +0800 Subject: [PATCH 106/118] Update stub file. --- cython/python_solvespace/slvs.pyi | 46 +++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 8c2f05f8c..2d576b41a 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- from typing import Tuple, List, Sequence, Counter, ClassVar +from enum import IntEnum, auto def quaternion_u( qw: float, @@ -36,6 +37,51 @@ def make_quaternion( ) -> Tuple[float, float, float, float]: ... +class Constraint(IntEnum): + """Symbol of the 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): + """Symbol of the result flags.""" + OKAY = 0 + INCONSISTENT = auto() + DIDNT_CONVERGE = auto() + TOO_MANY_UNKNOWNS = auto() + class Params: pass From 9520bff50b4d26cd5aacb4fa8b4ba5a608633731 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 9 Feb 2022 11:37:14 +0800 Subject: [PATCH 107/118] Fix build command. --- .github/workflows/python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 75b30b0f7..1a94568bc 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -37,7 +37,7 @@ jobs: python -m unittest - name: Pack working-directory: cython - run: python -m build + run: python -m build --sdist --wheel - if: matrix.os == 'ubuntu-latest' name: Upload artifact uses: actions/upload-artifact@v2 From 2a3186760b7714a89ff49496966d71693bece03f Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Wed, 9 Feb 2022 23:32:05 +0800 Subject: [PATCH 108/118] Bump version and update readme. --- cython/README.md | 4 ++-- cython/python_solvespace/__init__.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cython/README.md b/cython/README.md index b81552468..32caef0ba 100644 --- a/cython/README.md +++ b/cython/README.md @@ -59,12 +59,12 @@ pip install python-solvespace First build and install the module from the repo: ```bash -git submodule update --init extlib/mimalloc +git submodule update --init extlib/mimalloc extlib/eigen cd cython pip install -e . ``` -Build the module: +Rebuild the module: ```bash pip install -e . --no-deps diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 46aa07f98..4a1631df1 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.6" +__version__ = "3.0.7" from .slvs import ( quaternion_u, From 015810c2140aaa74b6251572eb204b87b83f7558 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Tue, 10 May 2022 15:01:25 +0800 Subject: [PATCH 109/118] Fix and patch constraints. --- cython/python_solvespace/slvs.pxd | 13 ++-- cython/python_solvespace/slvs.pyi | 29 +++------ cython/python_solvespace/slvs.pyx | 101 ++++++++++++------------------ 3 files changed, 55 insertions(+), 88 deletions(-) diff --git a/cython/python_solvespace/slvs.pxd b/cython/python_solvespace/slvs.pxd index 0394befff..74f4ac317 100644 --- a/cython/python_solvespace/slvs.pxd +++ b/cython/python_solvespace/slvs.pxd @@ -291,20 +291,21 @@ cdef class SolverSystem: 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 equal_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 diameter(self, Entity e1, double value) 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 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 = *) + cpdef void length_diff(self, Entity e1, Entity e2, double value, Entity wp = *) diff --git a/cython/python_solvespace/slvs.pyi b/cython/python_solvespace/slvs.pyi index 2d576b41a..6b51fee26 100644 --- a/cython/python_solvespace/slvs.pyi +++ b/cython/python_solvespace/slvs.pyi @@ -257,13 +257,13 @@ class SolverSystem: def equal(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: ... - def equal_included_angle( + def equal_angle( self, e1: Entity, e2: Entity, e3: Entity, e4: Entity, - wp: Entity + wp: Entity = Entity.FREE_IN_3D ) -> None: ... @@ -273,20 +273,14 @@ class SolverSystem: e2: Entity, e3: Entity, e4: Entity, - wp: Entity + wp: Entity = Entity.FREE_IN_3D ) -> None: ... - def ratio(self, e1: Entity, e2: Entity, value: float, wp: Entity) -> None: + def ratio(self, e1: Entity, e2: Entity, value: float, wp: Entity = Entity.FREE_IN_3D) -> None: ... - def symmetric( - self, - e1: Entity, - e2: Entity, - e3: Entity = Entity.NONE, - wp: Entity = Entity.FREE_IN_3D - ) -> None: + def symmetric(self, e1: Entity, e2: Entity, e3: Entity = Entity.NONE, wp: Entity = Entity.FREE_IN_3D) -> None: ... def symmetric_h(self, e1: Entity, e2: Entity, wp: Entity) -> None: @@ -295,12 +289,7 @@ class SolverSystem: def symmetric_v(self, e1: Entity, e2: Entity, wp: Entity) -> None: ... - def midpoint( - self, - e1: Entity, - e2: Entity, - wp: Entity = Entity.FREE_IN_3D - ) -> None: + def midpoint(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: ... def horizontal(self, e1: Entity, wp: Entity) -> None: @@ -309,16 +298,16 @@ class SolverSystem: def vertical(self, e1: Entity, wp: Entity) -> None: ... - def diameter(self, e1: Entity, value: float, wp: Entity) -> None: + def diameter(self, e1: Entity, value: float) -> None: ... def same_orientation(self, e1: Entity, e2: Entity) -> None: ... - def angle(self, e1: Entity, e2: Entity, value: float, wp: Entity, inverse: bool = False) -> None: + def angle(self, e1: Entity, e2: Entity, value: float, wp: Entity = Entity.FREE_IN_3D, inverse: bool = False) -> None: ... - def perpendicular(self, e1: Entity, e2: Entity, wp: Entity, inverse: bool = False) -> None: + def perpendicular(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D, inverse: bool = False) -> None: ... def parallel(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: diff --git a/cython/python_solvespace/slvs.pyx b/cython/python_solvespace/slvs.pyx index 55db88ba7..43a5a75f9 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/python_solvespace/slvs.pyx @@ -719,19 +719,14 @@ cdef class SolverSystem: | [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) + self.add_constraint(SLVS_C_POINTS_COINCIDENT, wp, 0., e1, e2, _E_NONE, _E_NONE) + elif e1.is_point() and e2.is_work_plane(): + self.add_constraint(SLVS_C_PT_IN_PLANE, _E_FREE_IN_3D, 0., e1, _E_NONE, e2, _E_NONE) + elif e1.is_point() and e2.is_line(): + self.add_constraint(SLVS_C_PT_ON_LINE, wp, 0., e1, _E_NONE, e2, _E_NONE) + elif e1.is_point() and e2.is_circle(): + self.add_constraint(SLVS_C_PT_ON_CIRCLE, _E_FREE_IN_3D, 0., e1, _E_NONE, e2, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") @@ -757,14 +752,11 @@ cdef class SolverSystem: 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) + 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) + 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) + 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}") @@ -792,20 +784,18 @@ cdef class SolverSystem: else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") - cpdef void equal_included_angle( + cpdef void equal_angle( self, Entity e1, Entity e2, Entity e3, Entity e4, - Entity wp + Entity wp = _E_FREE_IN_3D ): """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) @@ -818,25 +808,18 @@ cdef class SolverSystem: Entity e2, Entity e3, Entity e4, - Entity wp + Entity wp = _E_FREE_IN_3D ): """Constraint that point 1 (`e1`) and line 1 (`e2`), - point 2 (`e3`) and line 2 (`e4`) must have same distance on work - plane `wp`. + 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") + cpdef void ratio(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D): + """The ratio (`value`) constraint between two 2D lines (`e1` and `e2`).""" if e1.is_line_2d() and e2.is_line_2d(): self.add_constraint(SLVS_C_LENGTH_RATIO, wp, value, _E_NONE, _E_NONE, e1, e2) else: @@ -855,7 +838,7 @@ cdef class SolverSystem: |:---------------:|:---------------:|:---------------:|:-----------------:| | [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] | + | [is_point_2d] | [is_point_2d] | [is_line_2d] | 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) @@ -870,8 +853,7 @@ cdef class SolverSystem: 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]). + 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") @@ -882,8 +864,7 @@ cdef class SolverSystem: 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]). + 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") @@ -928,7 +909,7 @@ cdef class SolverSystem: else: raise TypeError(f"unsupported entities: {e1}, {wp}") - cpdef void diameter(self, Entity e1, double value, Entity wp): + cpdef void diameter(self, Entity e1, double value): """Diameter (`value`) constraint of a circular entities. | Entity 1 (`e1`) | Work plane (`wp`) | @@ -936,43 +917,34 @@ cdef class SolverSystem: | [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, + self.add_constraint(SLVS_C_DIAMETER, _E_FREE_IN_3D, value, _E_NONE, _E_NONE, e1, _E_NONE) else: - raise TypeError(f"unsupported entities: {e1}, {wp}") + raise TypeError(f"unsupported entities: {e1}") cpdef void same_orientation(self, Entity e1, Entity e2): - """Equal orientation constraint between two 3d normals (`e1` and - `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): + cpdef void angle(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D, 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): + cpdef void perpendicular(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D, 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. + on the work plane (`wp`) 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) @@ -994,11 +966,9 @@ cdef class SolverSystem: | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) | |:---------------:|:---------------:|:-----------------:| - | [is_arc] | [is_line_2d] | Is not [Entity.FREE_IN_3D] | + | [is_arc] | [is_line_2d] | 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 | + | [is_arc]/[is_cubic] | [is_arc]/[is_cubic] | not [Entity.FREE_IN_3D] | """ if e1.is_arc() and e2.is_line_2d(): if wp is _E_FREE_IN_3D: @@ -1007,7 +977,7 @@ cdef class SolverSystem: 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: + if 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: @@ -1015,10 +985,10 @@ cdef class SolverSystem: cpdef void distance_proj(self, Entity e1, Entity e2, double value): """Projected distance (`value`) constraint between - two 3d points (`e1` and `e2`). + two 2D/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, + if e1.is_point() and e2.is_point(): + self.add_constraint(SLVS_C_PROJ_PT_DISTANCE, _E_FREE_IN_3D, value, e1, e2, _E_NONE, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {e2}") @@ -1029,3 +999,10 @@ cdef class SolverSystem: self.add_constraint(SLVS_C_WHERE_DRAGGED, wp, 0., e1, _E_NONE, _E_NONE, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {wp}") + + cpdef void length_diff(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D): + """The length difference between two lines (`e1` and `e2`).""" + if e1.is_line() and e2.is_line(): + self.add_constraint(SLVS_C_LENGTH_DIFFERENCE, wp, value, _E_NONE, _E_NONE, e1, e2) + else: + raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") From 292a75d51c6d40b2f67f4b67f77939bb185f782c Mon Sep 17 00:00:00 2001 From: Yuan Chang Date: Thu, 12 May 2022 11:22:42 +0800 Subject: [PATCH 110/118] Fix function called. --- cython/test/test_slvs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/test/test_slvs.py b/cython/test/test_slvs.py index cfd05d152..c08d11baf 100644 --- a/cython/test/test_slvs.py +++ b/cython/test/test_slvs.py @@ -219,7 +219,7 @@ def test_pydemo(self): sys.equal(a401, c402, wp200) # The arc has radius 17.0 units. - sys.diameter(a401, 17 * 2, wp200) + sys.diameter(a401, 17 * 2) # If the solver fails, then ask it to report which constraints caused # the problem. From 8df4f051c14dab75ff9700fab7ffb4920d284f77 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 11 Nov 2022 19:33:58 +0800 Subject: [PATCH 111/118] Fix missing Eigen headers. --- cython/MANIFEST.in | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cython/MANIFEST.in b/cython/MANIFEST.in index 400ce8828..e7bddb14f 100644 --- a/cython/MANIFEST.in +++ b/cython/MANIFEST.in @@ -1,3 +1,3 @@ include pyproject.toml setup.cfg include README.md -recursive-include . *.h *.c +recursive-include . *.h *.c */Eigen/* From 704c103ad547904a51532417d78043221cd55881 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 11 Nov 2022 19:46:57 +0800 Subject: [PATCH 112/118] Update GitHub action. --- .github/workflows/python.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 1a94568bc..02b2a002c 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -15,6 +15,7 @@ jobs: - "3.8" - "3.9" - "3.10" + - "3.11" os: - ubuntu-latest - macos-latest @@ -37,7 +38,7 @@ jobs: python -m unittest - name: Pack working-directory: cython - run: python -m build --sdist --wheel + run: python -m build - if: matrix.os == 'ubuntu-latest' name: Upload artifact uses: actions/upload-artifact@v2 From b22a5c5d6fd3816ca6f3f4b4e8dcb41222faa0c2 Mon Sep 17 00:00:00 2001 From: KmolYuan Date: Fri, 11 Nov 2022 19:48:48 +0800 Subject: [PATCH 113/118] Bump version. --- cython/python_solvespace/__init__.py | 2 +- cython/setup.cfg | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/cython/python_solvespace/__init__.py b/cython/python_solvespace/__init__.py index 4a1631df1..c9fb9846f 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/python_solvespace/__init__.py @@ -8,7 +8,7 @@ __copyright__ = "Copyright (C) 2016-2019" __license__ = "GPLv3+" __email__ = "pyslvs@gmail.com" -__version__ = "3.0.7" +__version__ = "3.0.8" from .slvs import ( quaternion_u, diff --git a/cython/setup.cfg b/cython/setup.cfg index c4b757d22..d09438ea6 100644 --- a/cython/setup.cfg +++ b/cython/setup.cfg @@ -15,6 +15,7 @@ classifiers = Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Programming Language :: Cython Topic :: Scientific/Engineering License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) From 8d88a0b736f256a40f59381aba85bbb1dd1462ed Mon Sep 17 00:00:00 2001 From: Koen Schmeets Date: Sat, 7 Jan 2023 14:52:30 +0100 Subject: [PATCH 114/118] Use cibuildwheel --- .github/workflows/python.yml | 223 +++++++++--- cython/COPYING.txt | 675 +++++++++++++++++++++++++++++++++++ cython/pyproject.toml | 47 +++ 3 files changed, 897 insertions(+), 48 deletions(-) create mode 100644 cython/COPYING.txt diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 02b2a002c..667c91ef7 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -6,58 +6,185 @@ on: tags: [ py* ] jobs: - python-build: + python: + name: ${{ matrix.os_short }} python ${{ matrix.architecture }} cp${{ matrix.python_version }} + runs-on: ${{ matrix.os }} + timeout-minutes: 240 strategy: + fail-fast: false matrix: - pyver: - - "3.6" - - "3.7" - - "3.8" - - "3.9" - - "3.10" - - "3.11" - os: - - ubuntu-latest - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - env: - TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} - TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} + os_arch: [ + "windows-ia32", + "windows-x64", + # "windows-arm64", + "macos-x86_64", + "macos-arm64", + "linux-x86_64", + "linux-aarch64", + ] + python_version: [ + "37", + "38", + "39", + "310", + "311", + ] + exclude: + - os_arch: "macos-arm64" + python_version: "37" + include: + - os_arch: "windows-ia32" + os: "windows-2022" + os_short: "windows" + architecture: "ia32" + cibuildwheel_architecture: "x86" + cmake_generator_platform: "Win32" + - os_arch: "windows-x64" + os: "windows-2022" + os_short: "windows" + architecture: "x64" + cibuildwheel_architecture: "AMD64" + cmake_generator_platform: "x64" + # - os_arch: "windows-arm64" + # os: "windows-2022" + # os_short: "windows" + # architecture: "arm64" + # cibuildwheel_architecture: "ARM64" + # cmake_generator_platform: "ARM64" + - os_arch: "macos-x86_64" + os: "macos-11.0" + os_short: "macos" + cibuildwheel_architecture: "x86_64" + architecture: "x86_64" + - os_arch: "macos-arm64" + os: "macos-11.0" + os_short: "macos" + cibuildwheel_architecture: "arm64" + architecture: "arm64" + - os_arch: linux-x86_64 + os: "ubuntu-22.04" + os_short: "linux" + cibuildwheel_architecture: "x86_64" + architecture: "x86_64" + - os_arch: linux-aarch64 + os: "ubuntu-22.04" + os_short: "linux" + cibuildwheel_architecture: "aarch64" + architecture: "aarch64" + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: true + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + if: matrix.architecture == 'aarch64' + with: + platforms: arm64 + - name: Build wheels + uses: pypa/cibuildwheel@v2.11.2 + with: + package-dir: cython + env: + CIBW_BUILD: "cp${{ matrix.python_version }}-*" + CIBW_PLATFORM: "${{ matrix.os_short }}" + CIBW_BUILD_VERBOSITY: "1" + CIBW_ARCHS: "${{ matrix.cibuildwheel_architecture }}" + CIBW_ENVIRONMENT_WINDOWS: > + CMAKE_GENERATOR="Visual Studio 17 2022" + CMAKE_GENERATOR_PLATFORM="${{ matrix.cmake_generator_platform }}" + CIBW_ENVIRONMENT_PASS_WINDOWS: "CMAKE_GENERATOR CMAKE_GENERATOR_PLATFORM" + CIBW_ENVIRONMENT_MACOS: > + OPENMP_PREFIX_MACOS="/tmp/libomp/libomp/fixed" + CIBW_ENVIRONMENT_PASS_MACOS: "OPENMP_PREFIX_MACOS" + CIBW_ENVIRONMENT_LINUX: > + CMAKE_GENERATOR="Unix Makefiles" + CIBW_ENVIRONMENT_PASS_LINUX: "CMAKE_GENERATOR" + - uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.os_short }}-${{ matrix.architecture }} + path: | + ./wheelhouse/*.whl + publish-pypi: + name: publish to PyPi + needs: [ + python, + ] + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 - - run: git submodule update --init extlib/mimalloc extlib/eigen - - uses: actions/setup-python@v2 + - uses: actions/download-artifact@v3 with: - python-version: ${{ matrix.pyver }} - - name: Build and test - working-directory: cython + path: prebuilds + - name: prepare + shell: bash run: | - python -m pip install -U pip setuptools wheel build - python -m pip install -e . - python -m unittest - - name: Pack - working-directory: cython - run: python -m build - - if: matrix.os == 'ubuntu-latest' - name: Upload artifact - uses: actions/upload-artifact@v2 + mkdir dist + ls prebuilds + mv prebuilds/*/* dist + ls dist + - name: Publish wheels to Test PyPI + uses: pypa/gh-action-pypi-publish@release/v1 with: - name: ${{ matrix.os }}-py${{ matrix.pyver }} - path: cython/dist/*.tar.gz - - if: matrix.os != 'ubuntu-latest' - name: Upload artifact - uses: actions/upload-artifact@v2 + password: ${{ secrets.TEST_PYPI_API_TOKEN }} + repository_url: https://test.pypi.org/legacy/ + - name: Publish wheels to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@release/v1 with: - name: ${{ matrix.os }}-py${{ matrix.pyver }} - path: cython/dist/*.whl - - if: startsWith(github.ref, 'refs/tags/py') - run: python -m pip install twine - - if: startsWith(github.ref, 'refs/tags/py') && matrix.os == 'ubuntu-latest' - name: release - working-directory: cython - run: python -m twine upload "dist/*.tar.gz" --skip-existing - - if: startsWith(github.ref, 'refs/tags/py') && matrix.os != 'ubuntu-latest' - name: release - working-directory: cython - run: python -m twine upload "dist/*.whl" --skip-existing + password: ${{ secrets.PYPI_API_TOKEN }} + + # python-build: + # strategy: + # matrix: + # pyver: + # - "3.6" + # - "3.7" + # - "3.8" + # - "3.9" + # - "3.10" + # - "3.11" + # os: + # - ubuntu-latest + # - macos-latest + # - windows-latest + # runs-on: ${{ matrix.os }} + # env: + # TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} + # TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} + # steps: + # - uses: actions/checkout@v2 + # - run: git submodule update --init extlib/mimalloc extlib/eigen + # - uses: actions/setup-python@v2 + # with: + # python-version: ${{ matrix.pyver }} + # - name: Build and test + # working-directory: cython + # run: | + # python -m pip install -U pip setuptools wheel build + # python -m pip install -e . + # python -m unittest + # - name: Pack + # working-directory: cython + # run: python -m build + # - if: matrix.os == 'ubuntu-latest' + # name: Upload artifact + # uses: actions/upload-artifact@v2 + # with: + # name: ${{ matrix.os }}-py${{ matrix.pyver }} + # path: cython/dist/*.tar.gz + # - if: matrix.os != 'ubuntu-latest' + # name: Upload artifact + # uses: actions/upload-artifact@v2 + # with: + # name: ${{ matrix.os }}-py${{ matrix.pyver }} + # path: cython/dist/*.whl + # - if: startsWith(github.ref, 'refs/tags/py') + # run: python -m pip install twine + # - if: startsWith(github.ref, 'refs/tags/py') && matrix.os == 'ubuntu-latest' + # name: release + # working-directory: cython + # run: python -m twine upload "dist/*.tar.gz" --skip-existing + # - if: startsWith(github.ref, 'refs/tags/py') && matrix.os != 'ubuntu-latest' + # name: release + # working-directory: cython + # run: python -m twine upload "dist/*.whl" --skip-existing diff --git a/cython/COPYING.txt b/cython/COPYING.txt new file mode 100644 index 000000000..a737dcfed --- /dev/null +++ b/cython/COPYING.txt @@ -0,0 +1,675 @@ + + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/cython/pyproject.toml b/cython/pyproject.toml index 10376dcbe..e2f968540 100644 --- a/cython/pyproject.toml +++ b/cython/pyproject.toml @@ -1,3 +1,50 @@ +[project] +name = "solvespace" +version = "3.1.0" +description = "Parametric 2d/3d CAD solver" +readme = "README.md" +requires-python = ">=3.6" +keywords = ["cad", "mechanical-engineering", "2d", "3d"] +license = { file = "COPYING.txt" } +authors = [ + { name = "Jonathan Westhues", email="jwesthues@cq.cx" } +] +maintainers = [ + { name = "Yuan Chang", email="pyslvs@gmail.com" } +] + +classifiers = [ + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Cython", + "Topic :: Scientific/Engineering", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Operating System :: OS Independent", + "Typing :: Typed" +] + +[project.urls] +homepage = "https://github.com/solvespace/solvespace" +documentation = "https://github.com/solvespace/solvespace" +repository = "https://github.com/solvespace/solvespace" + [build-system] requires = ["wheel", "setuptools"] build-backend = "setuptools.build_meta" + +[tool.cibuildwheel] +build = ["cp37*", "cp38*", "cp39*", "cp310*", "cp311*"] +skip = ["pp*", "*-musllinux*", "*-manylinux_i686"] + +[tool.cibuildwheel.windows] +archs = ["AMD64", "x86", "ARM64"] + +[tool.cibuildwheel.linux] +archs = ["x86_64", "aarch64"] + +[tool.cibuildwheel.macos] +archs = ["x86_64", "arm64"] \ No newline at end of file From 5f908262dc19dab34e9fb6c9057a15e664e2cbfa Mon Sep 17 00:00:00 2001 From: Koen Schmeets Date: Sat, 7 Jan 2023 15:16:43 +0100 Subject: [PATCH 115/118] Bring back sdist --- .github/workflows/python.yml | 88 +++++++++++------------------------- 1 file changed, 26 insertions(+), 62 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 667c91ef7..13fd4fb7d 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -2,11 +2,30 @@ name: Python on: push: - branches: [ python ] - tags: [ py* ] + branches: [ master, python ] + tags: [ v* ] jobs: - python: + python-sdist: + name: python sdist + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: git submodule update --init extlib/mimalloc extlib/eigen + - uses: actions/setup-python@v3 + with: + python-version: "3.10" + - name: Pack + working-directory: cython + run: | + python -m pip install -U setuptools build + python -m build --sdist + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: sdist + path: cython/dist/*.tar.gz + python-wheel: name: ${{ matrix.os_short }} python ${{ matrix.architecture }} cp${{ matrix.python_version }} runs-on: ${{ matrix.os }} timeout-minutes: 240 @@ -75,7 +94,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - submodules: true + - run: git submodule update --init extlib/mimalloc extlib/eigen - name: Set up QEMU uses: docker/setup-qemu-action@v2 if: matrix.architecture == 'aarch64' @@ -102,13 +121,14 @@ jobs: CIBW_ENVIRONMENT_PASS_LINUX: "CMAKE_GENERATOR" - uses: actions/upload-artifact@v3 with: - name: ${{ matrix.os_short }}-${{ matrix.architecture }} + name: wheel-${{ matrix.os_short }}-${{ matrix.architecture }} path: | ./wheelhouse/*.whl publish-pypi: name: publish to PyPi needs: [ - python, + python-sdist, + python-wheel, ] runs-on: ubuntu-22.04 steps: @@ -132,59 +152,3 @@ jobs: uses: pypa/gh-action-pypi-publish@release/v1 with: password: ${{ secrets.PYPI_API_TOKEN }} - - # python-build: - # strategy: - # matrix: - # pyver: - # - "3.6" - # - "3.7" - # - "3.8" - # - "3.9" - # - "3.10" - # - "3.11" - # os: - # - ubuntu-latest - # - macos-latest - # - windows-latest - # runs-on: ${{ matrix.os }} - # env: - # TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} - # TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} - # steps: - # - uses: actions/checkout@v2 - # - run: git submodule update --init extlib/mimalloc extlib/eigen - # - uses: actions/setup-python@v2 - # with: - # python-version: ${{ matrix.pyver }} - # - name: Build and test - # working-directory: cython - # run: | - # python -m pip install -U pip setuptools wheel build - # python -m pip install -e . - # python -m unittest - # - name: Pack - # working-directory: cython - # run: python -m build - # - if: matrix.os == 'ubuntu-latest' - # name: Upload artifact - # uses: actions/upload-artifact@v2 - # with: - # name: ${{ matrix.os }}-py${{ matrix.pyver }} - # path: cython/dist/*.tar.gz - # - if: matrix.os != 'ubuntu-latest' - # name: Upload artifact - # uses: actions/upload-artifact@v2 - # with: - # name: ${{ matrix.os }}-py${{ matrix.pyver }} - # path: cython/dist/*.whl - # - if: startsWith(github.ref, 'refs/tags/py') - # run: python -m pip install twine - # - if: startsWith(github.ref, 'refs/tags/py') && matrix.os == 'ubuntu-latest' - # name: release - # working-directory: cython - # run: python -m twine upload "dist/*.tar.gz" --skip-existing - # - if: startsWith(github.ref, 'refs/tags/py') && matrix.os != 'ubuntu-latest' - # name: release - # working-directory: cython - # run: python -m twine upload "dist/*.whl" --skip-existing From 65b596be5692ab2191235fbb1702378c025ab5ac Mon Sep 17 00:00:00 2001 From: Koen Schmeets Date: Thu, 12 Jan 2023 18:44:20 +0100 Subject: [PATCH 116/118] Rename python_solvespace to solvepace --- cython/.gitignore | 8 ++++---- cython/README.md | 8 ++++---- cython/setup.cfg | 6 +++--- cython/setup.py | 6 +++--- cython/{python_solvespace => solvespace}/__init__.pxd | 0 cython/{python_solvespace => solvespace}/__init__.py | 2 +- cython/{python_solvespace => solvespace}/py.typed | 0 cython/{python_solvespace => solvespace}/slvs.pxd | 0 cython/{python_solvespace => solvespace}/slvs.pyi | 0 cython/{python_solvespace => solvespace}/slvs.pyx | 2 +- cython/test/test_slvs.py | 6 +++--- 11 files changed, 19 insertions(+), 19 deletions(-) rename cython/{python_solvespace => solvespace}/__init__.pxd (100%) rename cython/{python_solvespace => solvespace}/__init__.py (92%) rename cython/{python_solvespace => solvespace}/py.typed (100%) rename cython/{python_solvespace => solvespace}/slvs.pxd (100%) rename cython/{python_solvespace => solvespace}/slvs.pyi (100%) rename cython/{python_solvespace => solvespace}/slvs.pyx (99%) diff --git a/cython/.gitignore b/cython/.gitignore index c5fabf457..4dbd38fa7 100644 --- a/cython/.gitignore +++ b/cython/.gitignore @@ -13,10 +13,10 @@ /debian/libslvs1-dev/ /obj-*/ /*.slvs -python_solvespace/*.cpp -python_solvespace/src/ -python_solvespace/include/ -python_solvespace/extlib/ +solvespace/*.cpp +solvespace/src/ +solvespace/include/ +solvespace/extlib/ # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/cython/README.md b/cython/README.md index 32caef0ba..45d2cfb06 100644 --- a/cython/README.md +++ b/cython/README.md @@ -1,7 +1,7 @@ [![PyPI](https://img.shields.io/pypi/v/python-solvespace.svg)](https://pypi.org/project/python-solvespace/) [![GitHub license](https://img.shields.io/badge/license-GPLv3+-blue.svg)](https://raw.githubusercontent.com/KmolYuan/solvespace/master/LICENSE) -# python-solvespace +# solvespace Python library from the solver of SolveSpace, an open source CAD software. @@ -11,7 +11,7 @@ Python library from the solver of SolveSpace, an open source CAD software. The example extracted from unit test: ```python -from python_solvespace import SolverSystem, ResultFlag +from solvespace import SolverSystem, ResultFlag sys = SolverSystem() wp = sys.create_2d_base() # Workplane (Entity) @@ -51,7 +51,7 @@ The entity and parameter handles should have the same lifetime to the solver. # Install ```bash -pip install python-solvespace +pip install solvespace ``` # Build and Test (Repository) @@ -79,5 +79,5 @@ python -m unittest Uninstall the module: ```bash -pip uninstall python-solvespace +pip uninstall solvespace ``` diff --git a/cython/setup.cfg b/cython/setup.cfg index d09438ea6..7f48ea3cf 100644 --- a/cython/setup.cfg +++ b/cython/setup.cfg @@ -1,6 +1,6 @@ [metadata] -name = python_solvespace -version = attr: python_solvespace.__version__ +name = solvespace +version = attr: solvespace.__version__ description = Python library of Solvespace. long_description = file: README.md long_description_content_type = text/markdown @@ -31,7 +31,7 @@ setup_requires = [options.package_data] * = *.pyi, *.pxd, *.pyx -python_solvespace = py.typed +solvespace = py.typed [options.packages.find] exclude = diff --git a/cython/setup.py b/cython/setup.py index a82af6b0b..e8389331a 100644 --- a/cython/setup.py +++ b/cython/setup.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -"""Compile the Cython libraries of Python-Solvespace.""" +"""Compile the Cython libraries of Solvespace.""" __author__ = "Yuan Chang" __copyright__ = "Copyright (C) 2016-2019" @@ -16,7 +16,7 @@ from distutils import file_util, dir_util from platform import system -m_path = 'python_solvespace' +m_path = 'solvespace' include_path = join(m_path, 'include') src_path = join(m_path, 'src') platform_path = join(src_path, 'platform') @@ -172,7 +172,7 @@ def run(self): setup(ext_modules=[Extension( - "python_solvespace.slvs", + "solvespace.slvs", sources, language="c++", include_dirs=[include_path, src_path, mimalloc_include_path, diff --git a/cython/python_solvespace/__init__.pxd b/cython/solvespace/__init__.pxd similarity index 100% rename from cython/python_solvespace/__init__.pxd rename to cython/solvespace/__init__.pxd diff --git a/cython/python_solvespace/__init__.py b/cython/solvespace/__init__.py similarity index 92% rename from cython/python_solvespace/__init__.py rename to cython/solvespace/__init__.py index c9fb9846f..73ef2a3a4 100644 --- a/cython/python_solvespace/__init__.py +++ b/cython/solvespace/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -"""'python_solvespace' module is a wrapper of +"""'solvespace' module is a wrapper of Python binding Solvespace solver libraries. """ diff --git a/cython/python_solvespace/py.typed b/cython/solvespace/py.typed similarity index 100% rename from cython/python_solvespace/py.typed rename to cython/solvespace/py.typed diff --git a/cython/python_solvespace/slvs.pxd b/cython/solvespace/slvs.pxd similarity index 100% rename from cython/python_solvespace/slvs.pxd rename to cython/solvespace/slvs.pxd diff --git a/cython/python_solvespace/slvs.pyi b/cython/solvespace/slvs.pyi similarity index 100% rename from cython/python_solvespace/slvs.pyi rename to cython/solvespace/slvs.pyi diff --git a/cython/python_solvespace/slvs.pyx b/cython/solvespace/slvs.pyx similarity index 99% rename from cython/python_solvespace/slvs.pyx rename to cython/solvespace/slvs.pyx index 43a5a75f9..96511e25c 100644 --- a/cython/python_solvespace/slvs.pyx +++ b/cython/solvespace/slvs.pyx @@ -344,7 +344,7 @@ cdef class Entity: cdef class SolverSystem: - """A solver system of Python-Solvespace. + """A solver system of Solvespace. The operation of entities and constraints are using the methods of this class. """ diff --git a/cython/test/test_slvs.py b/cython/test/test_slvs.py index c08d11baf..f6a0b8bbf 100644 --- a/cython/test/test_slvs.py +++ b/cython/test/test_slvs.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -"""This module will test the functions of Python-Solvespace.""" +"""This module will test the functions of Solvespace.""" __author__ = "Yuan Chang" __copyright__ = "Copyright (C) 2016-2019" @@ -9,7 +9,7 @@ from unittest import TestCase from math import radians -from python_solvespace import ResultFlag, SolverSystem, make_quaternion +from solvespace import ResultFlag, SolverSystem, make_quaternion class CoreTest(TestCase): @@ -158,7 +158,7 @@ def test_pydemo(self): in order to satisfy the constraints. Copyright 2008-2013 Jonathan Westhues. - Copyright 2016-2017 Yuan Chang [pyslvs@gmail.com] Python-Solvespace bundled. + Copyright 2016-2017 Yuan Chang [pyslvs@gmail.com] 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 From 50f1d1316ae7444dc12910717e05d3ea9a1abd42 Mon Sep 17 00:00:00 2001 From: Koen Schmeets Date: Fri, 13 Jan 2023 18:57:53 +0100 Subject: [PATCH 117/118] Return constraint index --- cython/solvespace/slvs.pxd | 44 +++++++-------- cython/solvespace/slvs.pyi | 42 +++++++------- cython/solvespace/slvs.pyx | 112 ++++++++++++++++++------------------- 3 files changed, 99 insertions(+), 99 deletions(-) diff --git a/cython/solvespace/slvs.pxd b/cython/solvespace/slvs.pxd index 74f4ac317..685a4947f 100644 --- a/cython/solvespace/slvs.pxd +++ b/cython/solvespace/slvs.pxd @@ -273,7 +273,7 @@ cdef class SolverSystem: 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( + cpdef int add_constraint( self, int c_type, Entity wp, @@ -288,24 +288,24 @@ cdef class SolverSystem: 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_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) - 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 = *) - cpdef void length_diff(self, Entity e1, Entity e2, double value, Entity wp = *) + cpdef int coincident(self, Entity e1, Entity e2, Entity wp = *) + cpdef int distance(self, Entity e1, Entity e2, double value, Entity wp = *) + cpdef int equal(self, Entity e1, Entity e2, Entity wp = *) + cpdef int equal_angle(self, Entity e1, Entity e2, Entity e3, Entity e4, Entity wp = *) + cpdef int equal_point_to_line(self, Entity e1, Entity e2, Entity e3, Entity e4, Entity wp = *) + cpdef int ratio(self, Entity e1, Entity e2, double value, Entity wp = *) + cpdef int symmetric(self, Entity e1, Entity e2, Entity e3 = *, Entity wp = *) + cpdef int symmetric_h(self, Entity e1, Entity e2, Entity wp) + cpdef int symmetric_v(self, Entity e1, Entity e2, Entity wp) + cpdef int midpoint(self, Entity e1, Entity e2, Entity wp = *) + cpdef int horizontal(self, Entity e1, Entity wp) + cpdef int vertical(self, Entity e1, Entity wp) + cpdef int diameter(self, Entity e1, double value) + cpdef int same_orientation(self, Entity e1, Entity e2) + cpdef int angle(self, Entity e1, Entity e2, double value, Entity wp = *, bint inverse = *) + cpdef int perpendicular(self, Entity e1, Entity e2, Entity wp = *, bint inverse = *) + cpdef int parallel(self, Entity e1, Entity e2, Entity wp = *) + cpdef int tangent(self, Entity e1, Entity e2, Entity wp = *) + cpdef int distance_proj(self, Entity e1, Entity e2, double value) + cpdef int dragged(self, Entity e1, Entity wp = *) + cpdef int length_diff(self, Entity e1, Entity e2, double value, Entity wp = *) diff --git a/cython/solvespace/slvs.pyi b/cython/solvespace/slvs.pyi index 6b51fee26..034daf9ae 100644 --- a/cython/solvespace/slvs.pyi +++ b/cython/solvespace/slvs.pyi @@ -239,10 +239,10 @@ class SolverSystem: e4: Entity = Entity.NONE, other: int = 0, other2: int = 0 - ) -> None: + ) -> int: ... - def coincident(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: + def coincident(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: ... def distance( @@ -251,10 +251,10 @@ class SolverSystem: e2: Entity, value: float, wp: Entity = Entity.FREE_IN_3D - ) -> None: + ) -> int: ... - def equal(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: + def equal(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: ... def equal_angle( @@ -264,7 +264,7 @@ class SolverSystem: e3: Entity, e4: Entity, wp: Entity = Entity.FREE_IN_3D - ) -> None: + ) -> int: ... def equal_point_to_line( @@ -274,50 +274,50 @@ class SolverSystem: e3: Entity, e4: Entity, wp: Entity = Entity.FREE_IN_3D - ) -> None: + ) -> int: ... - def ratio(self, e1: Entity, e2: Entity, value: float, wp: Entity = Entity.FREE_IN_3D) -> None: + def ratio(self, e1: Entity, e2: Entity, value: float, wp: Entity = Entity.FREE_IN_3D) -> int: ... - def symmetric(self, e1: Entity, e2: Entity, e3: Entity = Entity.NONE, wp: Entity = Entity.FREE_IN_3D) -> None: + def symmetric(self, e1: Entity, e2: Entity, e3: Entity = Entity.NONE, wp: Entity = Entity.FREE_IN_3D) -> int: ... - def symmetric_h(self, e1: Entity, e2: Entity, wp: Entity) -> None: + def symmetric_h(self, e1: Entity, e2: Entity, wp: Entity) -> int: ... - def symmetric_v(self, e1: Entity, e2: Entity, wp: Entity) -> None: + def symmetric_v(self, e1: Entity, e2: Entity, wp: Entity) -> int: ... - def midpoint(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: + def midpoint(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: ... - def horizontal(self, e1: Entity, wp: Entity) -> None: + def horizontal(self, e1: Entity, wp: Entity) -> int: ... - def vertical(self, e1: Entity, wp: Entity) -> None: + def vertical(self, e1: Entity, wp: Entity) -> int: ... - def diameter(self, e1: Entity, value: float) -> None: + def diameter(self, e1: Entity, value: float) -> int: ... - def same_orientation(self, e1: Entity, e2: Entity) -> None: + def same_orientation(self, e1: Entity, e2: Entity) -> int: ... - def angle(self, e1: Entity, e2: Entity, value: float, wp: Entity = Entity.FREE_IN_3D, inverse: bool = False) -> None: + def angle(self, e1: Entity, e2: Entity, value: float, wp: Entity = Entity.FREE_IN_3D, inverse: bool = False) -> int: ... - def perpendicular(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D, inverse: bool = False) -> None: + def perpendicular(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D, inverse: bool = False) -> int: ... - def parallel(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: + def parallel(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: ... - def tangent(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: + def tangent(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: ... - def distance_proj(self, e1: Entity, e2: Entity, value: float) -> None: + def distance_proj(self, e1: Entity, e2: Entity, value: float) -> int: ... - def dragged(self, e1: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: + def dragged(self, e1: Entity, wp: Entity = Entity.FREE_IN_3D) -> int: ... diff --git a/cython/solvespace/slvs.pyx b/cython/solvespace/slvs.pyx index 96511e25c..b984c7aef 100644 --- a/cython/solvespace/slvs.pyx +++ b/cython/solvespace/slvs.pyx @@ -656,7 +656,7 @@ cdef class SolverSystem: self.entity_list.push_back(Slvs_MakeWorkplane(self.eh(), self.g, origin.h, nm.h)) return Entity.create(&self.entity_list.back()) - cpdef void add_constraint( + cpdef int add_constraint( self, int c_type, Entity wp, @@ -704,12 +704,13 @@ cdef class SolverSystem: c.other = other c.other2 = other2 self.cons_list.push_back(c) + return self.cons_list.size() ##### # Constraint methods. ##### - cpdef void coincident(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D): + cpdef int coincident(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D): """Coincident two entities. | Entity 1 (`e1`) | Entity 2 (`e2`) | Work plane (`wp`) | @@ -720,17 +721,17 @@ cdef class SolverSystem: | [is_point] | [is_circle] | Optional | """ if e1.is_point() and e2.is_point(): - self.add_constraint(SLVS_C_POINTS_COINCIDENT, wp, 0., e1, e2, _E_NONE, _E_NONE) + return self.add_constraint(SLVS_C_POINTS_COINCIDENT, wp, 0., e1, e2, _E_NONE, _E_NONE) elif e1.is_point() and e2.is_work_plane(): - self.add_constraint(SLVS_C_PT_IN_PLANE, _E_FREE_IN_3D, 0., e1, _E_NONE, e2, _E_NONE) + return self.add_constraint(SLVS_C_PT_IN_PLANE, _E_FREE_IN_3D, 0., e1, _E_NONE, e2, _E_NONE) elif e1.is_point() and e2.is_line(): - self.add_constraint(SLVS_C_PT_ON_LINE, wp, 0., e1, _E_NONE, e2, _E_NONE) + return self.add_constraint(SLVS_C_PT_ON_LINE, wp, 0., e1, _E_NONE, e2, _E_NONE) elif e1.is_point() and e2.is_circle(): - self.add_constraint(SLVS_C_PT_ON_CIRCLE, _E_FREE_IN_3D, 0., e1, _E_NONE, e2, _E_NONE) + return self.add_constraint(SLVS_C_PT_ON_CIRCLE, _E_FREE_IN_3D, 0., e1, _E_NONE, e2, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") - cpdef void distance( + cpdef int distance( self, Entity e1, Entity e2, @@ -749,18 +750,17 @@ cdef class SolverSystem: | [is_point] | [is_line] | Optional | """ if value == 0.: - self.coincident(e1, e2, wp) - return + return self.coincident(e1, e2, wp) if e1.is_point() and e2.is_point(): - self.add_constraint(SLVS_C_PT_PT_DISTANCE, wp, value, e1, e2, _E_NONE, _E_NONE) + return 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) + return 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) + return 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): + cpdef int 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`) | @@ -774,17 +774,17 @@ cdef class SolverSystem: | [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, + return 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, + return 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) + return 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_angle( + cpdef int equal_angle( self, Entity e1, Entity e2, @@ -797,12 +797,12 @@ cdef class SolverSystem: plane `wp`. """ 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, + return 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( + cpdef int equal_point_to_line( self, Entity e1, Entity e2, @@ -814,18 +814,18 @@ cdef class SolverSystem: point 2 (`e3`) and line 2 (`e4`) must have same distance on work plane `wp`. """ 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) + return 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 = _E_FREE_IN_3D): + cpdef int ratio(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D): """The ratio (`value`) constraint between two 2D lines (`e1` and `e2`).""" if e1.is_line_2d() and e2.is_line_2d(): - self.add_constraint(SLVS_C_LENGTH_RATIO, wp, value, _E_NONE, _E_NONE, e1, e2) + return self.add_constraint(SLVS_C_LENGTH_RATIO, wp, value, _E_NONE, _E_NONE, e1, e2) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") - cpdef void symmetric( + cpdef int symmetric( self, Entity e1, Entity e2, @@ -841,39 +841,39 @@ cdef class SolverSystem: | [is_point_2d] | [is_point_2d] | [is_line_2d] | 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) + return 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) + return 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) + return 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): + cpdef int 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) + return 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): + cpdef int 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) + return 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( + cpdef int midpoint( self, Entity e1, Entity e2, @@ -883,33 +883,33 @@ cdef class SolverSystem: 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) + return 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): + cpdef int 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) + return 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): + cpdef int 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) + return 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): + cpdef int diameter(self, Entity e1, double value): """Diameter (`value`) constraint of a circular entities. | Entity 1 (`e1`) | Work plane (`wp`) | @@ -918,49 +918,49 @@ cdef class SolverSystem: | [is_circle] | Optional | """ if e1.is_arc() or e1.is_circle(): - self.add_constraint(SLVS_C_DIAMETER, _E_FREE_IN_3D, value, _E_NONE, _E_NONE, + return self.add_constraint(SLVS_C_DIAMETER, _E_FREE_IN_3D, value, _E_NONE, _E_NONE, e1, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}") - cpdef void same_orientation(self, Entity e1, Entity e2): + cpdef int 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., + return 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 = _E_FREE_IN_3D, bint inverse = False): + cpdef int angle(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D, 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 e1.is_line_2d() and e2.is_line_2d(): - self.add_constraint(SLVS_C_ANGLE, wp, value, _E_NONE, _E_NONE, + return 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 = _E_FREE_IN_3D, bint inverse = False): + cpdef int perpendicular(self, Entity e1, Entity e2, Entity wp = _E_FREE_IN_3D, bint inverse = False): """Perpendicular constraint between two 2d lines (`e1` and `e2`) on the work plane (`wp`) with `inverse` option. """ if e1.is_line_2d() and e2.is_line_2d(): - self.add_constraint(SLVS_C_PERPENDICULAR, wp, 0., _E_NONE, _E_NONE, + return 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): + cpdef int 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) + return 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): + cpdef int 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`). @@ -973,36 +973,36 @@ cdef class SolverSystem: 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) + return 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) + return 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 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) + return 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): + cpdef int distance_proj(self, Entity e1, Entity e2, double value): """Projected distance (`value`) constraint between two 2D/3D points (`e1` and `e2`). """ if e1.is_point() and e2.is_point(): - self.add_constraint(SLVS_C_PROJ_PT_DISTANCE, _E_FREE_IN_3D, + return self.add_constraint(SLVS_C_PROJ_PT_DISTANCE, _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): + cpdef int 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) + return self.add_constraint(SLVS_C_WHERE_DRAGGED, wp, 0., e1, _E_NONE, _E_NONE, _E_NONE) else: raise TypeError(f"unsupported entities: {e1}, {wp}") - cpdef void length_diff(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D): + cpdef int length_diff(self, Entity e1, Entity e2, double value, Entity wp = _E_FREE_IN_3D): """The length difference between two lines (`e1` and `e2`).""" if e1.is_line() and e2.is_line(): - self.add_constraint(SLVS_C_LENGTH_DIFFERENCE, wp, value, _E_NONE, _E_NONE, e1, e2) + return self.add_constraint(SLVS_C_LENGTH_DIFFERENCE, wp, value, _E_NONE, _E_NONE, e1, e2) else: raise TypeError(f"unsupported entities: {e1}, {e2}, {wp}") From 392cb86b0fb7c9c3ca3f8be84c387061347132fc Mon Sep 17 00:00:00 2001 From: Koen Schmeets Date: Fri, 13 Jan 2023 20:30:14 +0100 Subject: [PATCH 118/118] Add version from tag --- .github/workflows/python.yml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 13fd4fb7d..1d04054fd 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -95,6 +95,15 @@ jobs: with: fetch-depth: 0 - run: git submodule update --init extlib/mimalloc extlib/eigen + - name: Set version + shell: bash + run: | + if [[ "${GITHUB_REF}" == refs/tags/* ]]; then + version="${GITHUB_REF##*/}" + else + version="$(git describe --tags).dev${GITHUB_RUN_NUMBER}" + fi + cd cython && sed -i.bak "s/^version = .*/version = \"${version}\"/g" pyproject.toml && rm pyproject.toml.bak - name: Set up QEMU uses: docker/setup-qemu-action@v2 if: matrix.architecture == 'aarch64' @@ -113,9 +122,6 @@ jobs: CMAKE_GENERATOR="Visual Studio 17 2022" CMAKE_GENERATOR_PLATFORM="${{ matrix.cmake_generator_platform }}" CIBW_ENVIRONMENT_PASS_WINDOWS: "CMAKE_GENERATOR CMAKE_GENERATOR_PLATFORM" - CIBW_ENVIRONMENT_MACOS: > - OPENMP_PREFIX_MACOS="/tmp/libomp/libomp/fixed" - CIBW_ENVIRONMENT_PASS_MACOS: "OPENMP_PREFIX_MACOS" CIBW_ENVIRONMENT_LINUX: > CMAKE_GENERATOR="Unix Makefiles" CIBW_ENVIRONMENT_PASS_LINUX: "CMAKE_GENERATOR"