├── .gitignore ├── .travis.yml ├── Cython ├── DOC.txt ├── slvs.pxd └── slvs.pyx ├── LICENSE ├── Makefile ├── README.md ├── __init__.py ├── appveyor.yml ├── example ├── example_crank_rocker.slvs ├── example_jansen_linkage.slvs └── example_nut_cracker.slvs ├── include └── slvs.h ├── platform └── patch.diff ├── requirements.txt ├── setup.py ├── slvs.pyi ├── src ├── config.h ├── constraint.cpp ├── constrainteq.cpp ├── dsc.h ├── entity.cpp ├── expr.cpp ├── expr.h ├── lib.cpp ├── platform │ ├── platform.cpp │ ├── platform.h │ ├── unixutil.cpp │ └── w32util.cpp ├── polygon.h ├── render │ └── render.h ├── resource.h ├── sketch.h ├── solvespace.h ├── srf │ └── surface.h ├── system.cpp ├── ttf.h ├── ui.h └── util.cpp └── test_slvs.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.exe 2 | */cdemo 3 | 4 | slvs.py 5 | Cython/*.cpp 6 | *.o* 7 | *.def 8 | *.lib 9 | *.so 10 | *.pyd 11 | *.a 12 | *.cxx 13 | cdemo 14 | 15 | *__pycache__* 16 | *obj* 17 | 18 | *.eric6project* 19 | *.e4p 20 | *.e4q 21 | *.e6t 22 | *.ui 23 | 24 | .DS_Store 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | #Travis-Ci 2 | language: python 3 | 4 | matrix: 5 | include: 6 | 7 | - os: linux 8 | sudo: required 9 | python: "3.5" 10 | dist: xenial 11 | 12 | - os: linux 13 | sudo: required 14 | python: "3.6" 15 | dist: xenial 16 | 17 | - os: linux 18 | sudo: required 19 | python: "3.7" 20 | dist: xenial 21 | 22 | - os: osx 23 | osx_image: xcode10 24 | language: generic 25 | env: PYTHON=35 26 | 27 | - os: osx 28 | osx_image: xcode10 29 | language: generic 30 | env: PYTHON=36 31 | 32 | - os: osx 33 | osx_image: xcode10 34 | language: generic 35 | env: PYTHON=37 36 | 37 | # For OSX 38 | before_install: 39 | 40 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 41 | curl -LO https://raw.githubusercontent.com/GiovanniBussi/macports-ci/master/macports-ci; 42 | source ./macports-ci install; 43 | fi 44 | 45 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 46 | yes | sudo port install python$PYTHON; 47 | yes | sudo port install py$PYTHON-pip; 48 | sudo port select --set python3 python$PYTHON; 49 | sudo port select --set pip pip$PYTHON; 50 | export PATH=$PATH:$(python3 -c "import site; print(site.USER_BASE)")/bin; 51 | fi 52 | 53 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 54 | python3 --version; 55 | pip --version; 56 | fi 57 | 58 | - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then 59 | pip install -r requirements.txt --user; 60 | fi 61 | 62 | script: 63 | - make test 64 | 65 | before_cache: 66 | - rm -rf $HOME/.cache/pip/log 67 | 68 | cache: 69 | directories: 70 | - $HOME/.cache/pip 71 | -------------------------------------------------------------------------------- /Cython/DOC.txt: -------------------------------------------------------------------------------- 1 | 2 | INTRODUCTION 3 | ============ 4 | 5 | A sketch in SolveSpace consists of three basic elements: parameters, 6 | entities, and constraints. 7 | 8 | A parameter (Slvs_Param) is a single real number, represented internally 9 | by a double-precision floating point variable. The parameters are unknown 10 | variables that the solver modifies in order to satisfy the constraints. 11 | 12 | An entity (Slvs_Entity) is a geometric thing, like a point or a line 13 | segment or a circle. Entities are defined in terms of parameters, 14 | and in terms of other entities. For example, a point in three-space 15 | is represented by three parameters, corresponding to its x, y, and z 16 | coordinates in our base coordinate frame. A line segment is represented 17 | by two point entities, corresponding to its endpoints. 18 | 19 | A constraint (Slvs_Constraint) is a geometric property of an entity, 20 | or a relationship among multiple entities. For example, a point-point 21 | distance constraint will set the distance between two point entities. 22 | 23 | Parameters, entities, and constraints are typically referenced by their 24 | handles (Slvs_hParam, Slvs_hEntity, Slvs_hConstraint). These handles are 25 | 32-bit integer values starting from 1. The zero handle is reserved. Each 26 | object has a unique handle within its type (but it's acceptable, for 27 | example to have a constraint with an Slvs_hConstraint of 7, and also to 28 | have an entity with an Slvs_hEntity of 7). The use of handles instead 29 | of pointers helps to avoid memory corruption. 30 | 31 | Entities and constraints are assigned into groups. A group is a set of 32 | entities and constraints that is solved simultaneously. In a parametric 33 | CAD system, a single group would typically correspond to a single sketch. 34 | Constraints within a group may refer to entities outside that group, 35 | but only the entities within that group will be modified by the solver. 36 | 37 | Consider point A in group 1, and point B in group 2. We have a constraint 38 | in group 2 that makes the points coincident. When we solve group 2, the 39 | solver is allowed to move point B to place it on top of point A. It is 40 | not allowed to move point A to put it on top of point B, because point 41 | A is outside the group being solved. 42 | 43 | This corresponds to the typical structure of a parametric CAD system. In a 44 | later sketch, we may constrain our entities against existing geometry from 45 | earlier sketches. The constraints will move the entities in our current 46 | sketch, but will not change the geometry from the earlier sketches. 47 | 48 | To use the solver, we first define a set of parameters, entities, and 49 | constraints. We provide an initial guess for each parameter; this is 50 | necessary to achieve convergence, and also determines which solution 51 | gets chosen when (finitely many) multiple solutions exist. Typically, 52 | these initial guesses are provided by the initial configuration in which 53 | the user drew the entities before constraining them. 54 | 55 | We then run the solver for a given group. The entities within that group 56 | are modified in an attempt to satisfy the constraints. 57 | 58 | After running the solver, there are three possible outcomes: 59 | 60 | * All constraints were satisfied to within our numerical 61 | tolerance (i.e., success). The result is equal to SLVS_RESULT_OKAY, 62 | and the parameters in param[] have been updated. 63 | 64 | * The solver can prove that two constraints are inconsistent (for 65 | example, if a line with nonzero length is constrained both 66 | horizontal and vertical). In that case, a list of inconsistent 67 | constraints is generated in failed[]. 68 | 69 | * The solver cannot prove that two constraints are inconsistent, but 70 | it cannot find a solution. In that case, the list of unsatisfied 71 | constraints is generated in failed[]. 72 | 73 | 74 | TYPES OF ENTITIES 75 | ================= 76 | 77 | SLVS_E_POINT_IN_3D 78 | 79 | A point in 3d. Defined by three parameters: 80 | 81 | param[0] the point's x coordinate 82 | param[1] y 83 | param[1] z 84 | 85 | 86 | SLVS_E_POINT_IN_2D 87 | 88 | A point within a workplane. Defined by the workplane 89 | 90 | wrkpl 91 | 92 | and by two parameters 93 | 94 | param[0] the point's u coordinate 95 | param[1] v 96 | 97 | within the coordinate system of the workplane. For example, if the 98 | workplane is the zx plane, then u = z and v = x. If the workplane is 99 | parallel to the zx plane, but translated so that the workplane's 100 | origin is (3, 4, 5), then u = z - 5 and v = x - 3. 101 | 102 | 103 | SLVS_E_NORMAL_IN_3D 104 | 105 | A normal. In SolveSpace, "normals" represent a 3x3 rotation matrix 106 | from our base coordinate system to a new frame. Defined by the 107 | unit quaternion 108 | 109 | param[0] w 110 | param[1] x 111 | param[2] y 112 | param[3] z 113 | 114 | where the quaternion is given by w + x*i + y*j + z*k. 115 | 116 | It is useful to think of this quaternion as representing a plane 117 | through the origin. This plane has three associated vectors: basis 118 | vectors U, V that lie within the plane, and normal N that is 119 | perpendicular to it. This means that 120 | 121 | [ U V N ]' 122 | 123 | defines a 3x3 rotation matrix. So U, V, and N all have unit length, 124 | and are orthogonal so that 125 | 126 | U cross V = N 127 | V cross N = U 128 | N cross U = V 129 | 130 | Convenience functions (Slvs_Quaternion*) are provided to convert 131 | between this representation as vectors U, V, N and the unit 132 | quaternion. 133 | 134 | A unit quaternion has only 3 degrees of freedom, but is specified in 135 | terms of 4 parameters. An extra constraint is therefore generated 136 | implicitly, that 137 | 138 | w^2 + x^2 + y^2 + z^2 = 1 139 | 140 | 141 | SLVS_E_NORMAL_IN_2D 142 | 143 | A normal within a workplane. This is identical to the workplane's 144 | normal, so it is simply defined by 145 | 146 | wrkpl 147 | 148 | This entity type is used, for example, to define a circle that lies 149 | within a workplane. The circle's normal is the same as the workplane's 150 | normal, so we can use an SLVS_E_NORMAL_IN_2D to copy the workplane's 151 | normal. 152 | 153 | 154 | SLVS_E_DISTANCE 155 | 156 | A distance. This entity is used to define the radius of a circle, by 157 | a single parameter 158 | 159 | param[0] r 160 | 161 | 162 | SLVS_E_WORKPLANE 163 | 164 | An oriented plane, somewhere in 3d. This entity therefore has 6 165 | degrees of freedom: three translational, and three rotational. It is 166 | specified in terms of its origin 167 | 168 | point[0] origin 169 | 170 | and a normal 171 | 172 | normal 173 | 174 | The normal describes three vectors U, V, N, as discussed in the 175 | documentation for SLVS_E_NORMAL_IN_3D. The plane is therefore given 176 | by the equation 177 | 178 | p = origin + s*U + t*V 179 | 180 | for any scalar s and t. 181 | 182 | 183 | SLVS_E_LINE_SEGMENT 184 | 185 | A line segment between two endpoints 186 | 187 | point[0] 188 | point[1] 189 | 190 | 191 | SLVS_E_CUBIC 192 | 193 | A nonrational cubic Bezier segment 194 | 195 | point[0] starting point P0 196 | point[1] control point P1 197 | point[2] control point P2 198 | point[3] ending point P3 199 | 200 | The curve then has equation 201 | 202 | p(t) = P0*(1 - t)^3 + 3*P1*(1 - t)^2*t + 3*P2*(1 - t)*t^2 + P3*t^3 203 | 204 | as t goes from 0 to 1. 205 | 206 | 207 | SLVS_E_CIRCLE 208 | 209 | A complete circle. The circle lies within a plane with normal 210 | 211 | normal 212 | 213 | The circle is centered at 214 | 215 | point[0] 216 | 217 | The circle's radius is 218 | 219 | distance 220 | 221 | 222 | SLVS_E_ARC_OF_CIRCLE 223 | 224 | An arc of a circle. An arc must always lie within a workplane; it 225 | cannot be free in 3d. So it is specified with a workplane 226 | 227 | wrkpl 228 | 229 | It is then defined by three points 230 | 231 | point[0] center of the circle 232 | point[1] beginning of the arc 233 | point[2] end of the arc 234 | 235 | and its normal 236 | 237 | normal identical to the normal of the workplane 238 | 239 | The arc runs counter-clockwise from its beginning to its end (with 240 | the workplane's normal pointing towards the viewer). If the beginning 241 | and end of the arc are coincident, then the arc is considered to 242 | represent a full circle. 243 | 244 | This representation has an extra degree of freedom. An extra 245 | constraint is therefore generated implicitly, so that 246 | 247 | distance(center, beginning) = distance(center, end) 248 | 249 | 250 | TYPES OF CONSTRAINTS 251 | ==================== 252 | 253 | Many constraints can apply either in 3d, or in a workplane. This is 254 | determined by the wrkpl member of the constraint. If that member is set 255 | to SLVS_FREE_IN_3D, then the constraint applies in 3d. If that member 256 | is set equal to a workplane, the the constraint applies projected into 257 | that workplane. (For example, a constraint on the distance between two 258 | points actually applies to the projected distance). 259 | 260 | Constraints that may be used in 3d or projected into a workplane are 261 | marked with a single star (*). Constraints that must always be used with 262 | a workplane are marked with a double star (**). Constraints that ignore 263 | the wrkpl member are marked with no star. 264 | 265 | SLVS_C_PT_PT_DISTANCE* 266 | 267 | The distance between points ptA and ptB is equal to valA. This is an 268 | unsigned distance, so valA must always be positive. 269 | 270 | SLVS_C_PROJ_PT_DISTANCE 271 | 272 | The distance between points ptA and ptB, as projected along the line 273 | or normal entityA, is equal to valA. This is a signed distance. 274 | 275 | SLVS_C_POINTS_COINCIDENT* 276 | 277 | Points ptA and ptB are coincident (i.e., exactly on top of each 278 | other). 279 | 280 | SLVS_C_PT_PLANE_DISTANCE 281 | 282 | The distance from point ptA to workplane entityA is equal to 283 | valA. This is a signed distance; positive versus negative valA 284 | correspond to a point that is above vs. below the plane. 285 | 286 | SLVS_C_PT_LINE_DISTANCE* 287 | 288 | The distance from point ptA to line segment entityA is equal to valA. 289 | 290 | If the constraint is projected, then valA is a signed distance; 291 | positive versus negative valA correspond to a point that is above 292 | vs. below the line. 293 | 294 | If the constraint applies in 3d, then valA must always be positive. 295 | 296 | SLVS_C_PT_IN_PLANE 297 | 298 | The point ptA lies in plane entityA. 299 | 300 | SLVS_C_PT_ON_LINE* 301 | 302 | The point ptA lies on the line entityA. 303 | 304 | Note that this constraint removes one degree of freedom when projected 305 | in to the plane, but two degrees of freedom in 3d. 306 | 307 | SLVS_C_EQUAL_LENGTH_LINES* 308 | 309 | The lines entityA and entityB have equal length. 310 | 311 | SLVS_C_LENGTH_RATIO* 312 | 313 | The length of line entityA divided by the length of line entityB is 314 | equal to valA. 315 | 316 | SLVS_C_LENGTH_DIFFERENCE* 317 | 318 | The lengths of line entityA and line entityB differ by valA. 319 | 320 | SLVS_C_EQ_LEN_PT_LINE_D* 321 | 322 | The length of the line entityA is equal to the distance from point 323 | ptA to line entityB. 324 | 325 | SLVS_C_EQ_PT_LN_DISTANCES* 326 | 327 | The distance from the line entityA to the point ptA is equal to the 328 | distance from the line entityB to the point ptB. 329 | 330 | SLVS_C_EQUAL_ANGLE* 331 | 332 | The angle between lines entityA and entityB is equal to the angle 333 | between lines entityC and entityD. 334 | 335 | If other is true, then the angles are supplementary (i.e., theta1 = 336 | 180 - theta2) instead of equal. 337 | 338 | SLVS_C_EQUAL_LINE_ARC_LEN* 339 | 340 | The length of the line entityA is equal to the length of the circular 341 | arc entityB. 342 | 343 | SLVS_C_SYMMETRIC* 344 | 345 | The points ptA and ptB are symmetric about the plane entityA. This 346 | means that they are on opposite sides of the plane and at equal 347 | distances from the plane, and that the line connecting ptA and ptB 348 | is normal to the plane. 349 | 350 | SLVS_C_SYMMETRIC_HORIZ 351 | SLVS_C_SYMMETRIC_VERT** 352 | 353 | The points ptA and ptB are symmetric about the horizontal or vertical 354 | axis of the specified workplane. 355 | 356 | SLVS_C_SYMMETRIC_LINE** 357 | 358 | The points ptA and ptB are symmetric about the line entityA. 359 | 360 | SLVS_C_AT_MIDPOINT* 361 | 362 | The point ptA lies at the midpoint of the line entityA. 363 | 364 | SLVS_C_HORIZONTAL 365 | SLVS_C_VERTICAL** 366 | 367 | The line connecting points ptA and ptB is horizontal or vertical. Or, 368 | the line segment entityA is horizontal or vertical. If points are 369 | specified then the line segment should be left zero, and if a line 370 | is specified then the points should be left zero. 371 | 372 | SLVS_C_DIAMETER 373 | 374 | The diameter of circle or arc entityA is equal to valA. 375 | 376 | SLVS_C_PT_ON_CIRCLE 377 | 378 | The point ptA lies on the right cylinder obtained by extruding circle 379 | or arc entityA normal to its plane. 380 | 381 | SLVS_C_SAME_ORIENTATION 382 | 383 | The normals entityA and entityB describe identical rotations. This 384 | constraint therefore restricts three degrees of freedom. 385 | 386 | SLVS_C_ANGLE* 387 | 388 | The angle between lines entityA and entityB is equal to valA, where 389 | valA is specified in degrees. This constraint equation is written 390 | in the form 391 | 392 | (A dot B)/(|A||B|) = cos(valA) 393 | 394 | where A and B are vectors in the directions of lines A and B. This 395 | equation does not specify the angle unambiguously; for example, 396 | note that valA = +/- 90 degrees will produce the same equation. 397 | 398 | If other is true, then the constraint is instead that 399 | 400 | (A dot B)/(|A||B|) = -cos(valA) 401 | 402 | SLVS_C_PERPENDICULAR* 403 | 404 | Identical to SLVS_C_ANGLE with valA = 90 degrees. 405 | 406 | SLVS_C_PARALLEL* 407 | 408 | Lines entityA and entityB are parallel. 409 | 410 | Note that this constraint removes one degree of freedom when projected 411 | in to the plane, but two degrees of freedom in 3d. 412 | 413 | SLVS_C_ARC_LINE_TANGENT** 414 | 415 | The arc entityA is tangent to the line entityB. If other is false, 416 | then the arc is tangent at its beginning (point[1]). If other is true, 417 | then the arc is tangent at its end (point[2]). 418 | 419 | SLVS_C_CUBIC_LINE_TANGENT* 420 | 421 | The cubic entityA is tangent to the line entityB. The variable 422 | other indicates: 423 | 424 | if false: the cubic is tangent at its beginning 425 | if true: the cubic is tangent at its end 426 | 427 | The beginning of the cubic is point[0], and the end is point[3]. 428 | 429 | SLVS_C_CURVE_CURVE_TANGENT** 430 | 431 | The two entities entityA and entityB are tangent. These entities can 432 | each be either an arc or a cubic, in any combination. The flags 433 | other and other2 indicate which endpoint of the curve is tangent, 434 | for entityA and entityB respectively: 435 | 436 | if false: the entity is tangent at its beginning 437 | if true: the entity is tangent at its end 438 | 439 | For cubics, point[0] is the beginning, and point[3] is the end. For 440 | arcs, point[1] is the beginning, and point[2] is the end. 441 | 442 | SLVS_C_EQUAL_RADIUS 443 | 444 | The circles or arcs entityA and entityB have equal radius. 445 | 446 | SLVS_C_WHERE_DRAGGED* 447 | 448 | The point ptA is locked at its initial numerical guess, and cannot 449 | be moved. This constrains two degrees of freedom in a workplane, 450 | and three in free space. It's therefore possible for this constraint 451 | to overconstrain the sketch, for example if it's applied to a point 452 | with one remaining degree of freedom. 453 | 454 | 455 | USING THE SOLVER 456 | ================ 457 | 458 | The solver is provided as a DLL, and will be usable with most 459 | Windows-based developement tools. Examples are provided: 460 | 461 | in C/C++ - CDemo.c 462 | 463 | in VB.NET - VbDemo.vb 464 | 465 | 466 | Copyright 2009-2013 Jonathan Westhues. 467 | 468 | -------------------------------------------------------------------------------- /Cython/slvs.pxd: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # cython: language_level=3 3 | 4 | """Wrapper header of Solvespace. 5 | 6 | author: Yuan Chang 7 | copyright: Copyright (C) 2016-2019 8 | license: AGPL 9 | email: pyslvs@gmail.com 10 | """ 11 | 12 | from libc.stdint cimport uint32_t 13 | from libcpp.vector cimport vector 14 | from libcpp.map cimport map as cmap 15 | 16 | cdef extern from "slvs.h" nogil: 17 | 18 | ctypedef uint32_t Slvs_hParam 19 | ctypedef uint32_t Slvs_hEntity 20 | ctypedef uint32_t Slvs_hConstraint 21 | ctypedef uint32_t Slvs_hGroup 22 | 23 | # Virtual work plane entity 24 | Slvs_hEntity SLVS_FREE_IN_3D 25 | 26 | ctypedef struct Slvs_Param: 27 | Slvs_hParam h 28 | Slvs_hGroup group 29 | double val 30 | 31 | # Entity type 32 | int SLVS_E_POINT_IN_3D 33 | int SLVS_E_POINT_IN_2D 34 | 35 | int SLVS_E_NORMAL_IN_2D 36 | int SLVS_E_NORMAL_IN_3D 37 | 38 | int SLVS_E_DISTANCE 39 | 40 | int SLVS_E_WORKPLANE 41 | int SLVS_E_LINE_SEGMENT 42 | int SLVS_E_CUBIC 43 | int SLVS_E_CIRCLE 44 | int SLVS_E_ARC_OF_CIRCLE 45 | 46 | ctypedef struct Slvs_Entity: 47 | Slvs_hEntity h 48 | Slvs_hGroup group 49 | int type 50 | Slvs_hEntity wrkpl 51 | Slvs_hEntity point[4] 52 | Slvs_hEntity normal 53 | Slvs_hEntity distance 54 | Slvs_hParam param[4] 55 | 56 | ctypedef struct Slvs_Constraint: 57 | Slvs_hConstraint h 58 | Slvs_hGroup group 59 | int type 60 | Slvs_hEntity wrkpl 61 | double valA 62 | Slvs_hEntity ptA 63 | Slvs_hEntity ptB 64 | Slvs_hEntity entityA 65 | Slvs_hEntity entityB 66 | Slvs_hEntity entityC 67 | Slvs_hEntity entityD 68 | int other 69 | int other2 70 | 71 | ctypedef struct Slvs_System: 72 | Slvs_Param *param 73 | int params 74 | Slvs_Entity *entity 75 | int entities 76 | Slvs_Constraint *constraint 77 | int constraints 78 | Slvs_hParam dragged[4] 79 | int calculateFaileds 80 | Slvs_hConstraint *failed 81 | int faileds 82 | int dof 83 | int result 84 | 85 | void Slvs_Solve(Slvs_System *sys, Slvs_hGroup hg) 86 | void Slvs_QuaternionU( 87 | double qw, double qx, double qy, double qz, 88 | double *x, double *y, double *z 89 | ) 90 | void Slvs_QuaternionV( 91 | double qw, double qx, double qy, double qz, 92 | double *x, double *y, double *z 93 | ) 94 | void Slvs_QuaternionN( 95 | double qw, double qx, double qy, double qz, 96 | double *x, double *y, double *z 97 | ) 98 | void Slvs_MakeQuaternion( 99 | double ux, double uy, double uz, 100 | double vx, double vy, double vz, 101 | double *qw, double *qx, double *qy, double *qz 102 | ) 103 | Slvs_Param Slvs_MakeParam(Slvs_hParam h, Slvs_hGroup group, double val) 104 | Slvs_Entity Slvs_MakePoint2d( 105 | Slvs_hEntity h, Slvs_hGroup group, 106 | Slvs_hEntity wrkpl, 107 | Slvs_hParam u, Slvs_hParam v 108 | ) 109 | Slvs_Entity Slvs_MakePoint3d( 110 | Slvs_hEntity h, Slvs_hGroup group, 111 | Slvs_hParam x, Slvs_hParam y, Slvs_hParam z 112 | ) 113 | Slvs_Entity Slvs_MakeNormal3d( 114 | Slvs_hEntity h, Slvs_hGroup group, 115 | Slvs_hParam qw, Slvs_hParam qx, 116 | Slvs_hParam qy, Slvs_hParam qz 117 | ) 118 | Slvs_Entity Slvs_MakeNormal2d( 119 | Slvs_hEntity h, Slvs_hGroup group, 120 | Slvs_hEntity wrkpl 121 | ) 122 | Slvs_Entity Slvs_MakeDistance( 123 | Slvs_hEntity h, Slvs_hGroup group, 124 | Slvs_hEntity wrkpl, Slvs_hParam d 125 | ) 126 | Slvs_Entity Slvs_MakeLineSegment( 127 | Slvs_hEntity h, Slvs_hGroup group, 128 | Slvs_hEntity wrkpl, 129 | Slvs_hEntity ptA, Slvs_hEntity ptB 130 | ) 131 | Slvs_Entity Slvs_MakeCubic( 132 | Slvs_hEntity h, Slvs_hGroup group, 133 | Slvs_hEntity wrkpl, 134 | Slvs_hEntity pt0, Slvs_hEntity pt1, 135 | Slvs_hEntity pt2, Slvs_hEntity pt3 136 | ) 137 | Slvs_Entity Slvs_MakeArcOfCircle( 138 | Slvs_hEntity h, Slvs_hGroup group, 139 | Slvs_hEntity wrkpl, 140 | Slvs_hEntity normal, 141 | Slvs_hEntity center, 142 | Slvs_hEntity start, Slvs_hEntity end 143 | ) 144 | Slvs_Entity Slvs_MakeCircle( 145 | Slvs_hEntity h, Slvs_hGroup group, 146 | Slvs_hEntity wrkpl, 147 | Slvs_hEntity center, 148 | Slvs_hEntity normal, Slvs_hEntity radius 149 | ) 150 | Slvs_Entity Slvs_MakeWorkplane( 151 | Slvs_hEntity h, Slvs_hGroup group, 152 | Slvs_hEntity origin, Slvs_hEntity normal 153 | ) 154 | Slvs_Constraint Slvs_MakeConstraint( 155 | Slvs_hConstraint h, 156 | Slvs_hGroup group, 157 | int type, 158 | Slvs_hEntity wrkpl, 159 | double valA, 160 | Slvs_hEntity ptA, 161 | Slvs_hEntity ptB, 162 | Slvs_hEntity entityA, 163 | Slvs_hEntity entityB 164 | ) 165 | 166 | 167 | cpdef enum Constraint: 168 | # Expose macro of constraint types 169 | POINTS_COINCIDENT = 100000 170 | PT_PT_DISTANCE 171 | PT_PLANE_DISTANCE 172 | PT_LINE_DISTANCE 173 | PT_FACE_DISTANCE 174 | PT_IN_PLANE 175 | PT_ON_LINE 176 | PT_ON_FACE 177 | EQUAL_LENGTH_LINES 178 | LENGTH_RATIO 179 | EQ_LEN_PT_LINE_D 180 | EQ_PT_LN_DISTANCES 181 | EQUAL_ANGLE 182 | EQUAL_LINE_ARC_LEN 183 | SYMMETRIC 184 | SYMMETRIC_HORIZ 185 | SYMMETRIC_VERT 186 | SYMMETRIC_LINE 187 | AT_MIDPOINT 188 | HORIZONTAL 189 | VERTICAL 190 | DIAMETER 191 | PT_ON_CIRCLE 192 | SAME_ORIENTATION 193 | ANGLE 194 | PARALLEL 195 | PERPENDICULAR 196 | ARC_LINE_TANGENT 197 | CUBIC_LINE_TANGENT 198 | EQUAL_RADIUS 199 | PROJ_PT_DISTANCE 200 | WHERE_DRAGGED 201 | CURVE_CURVE_TANGENT 202 | LENGTH_DIFFERENCE 203 | 204 | 205 | cpdef enum ResultFlag: 206 | # Expose macro of result flags 207 | OKAY 208 | INCONSISTENT 209 | DIDNT_CONVERGE 210 | TOO_MANY_UNKNOWNS 211 | 212 | 213 | cpdef tuple quaternion_u(double qw, double qx, double qy, double qz) 214 | cpdef tuple quaternion_v(double qw, double qx, double qy, double qz) 215 | cpdef tuple quaternion_n(double qw, double qx, double qy, double qz) 216 | cpdef tuple make_quaternion(double ux, double uy, double uz, double vx, double vy, double vz) 217 | 218 | 219 | cdef class Params: 220 | 221 | cdef vector[Slvs_hParam] param_list 222 | 223 | @staticmethod 224 | cdef Params create(Slvs_hParam *p, size_t count) 225 | 226 | 227 | cdef class Entity: 228 | 229 | cdef int t 230 | cdef Slvs_hEntity h, wp 231 | cdef Slvs_hGroup g 232 | cdef readonly Params params 233 | 234 | @staticmethod 235 | cdef Entity create(Slvs_Entity *e, size_t p_size) 236 | 237 | cpdef bint is_3d(self) 238 | cpdef bint is_none(self) 239 | cpdef bint is_point_2d(self) 240 | cpdef bint is_point_3d(self) 241 | cpdef bint is_point(self) 242 | cpdef bint is_normal_2d(self) 243 | cpdef bint is_normal_3d(self) 244 | cpdef bint is_normal(self) 245 | cpdef bint is_distance(self) 246 | cpdef bint is_work_plane(self) 247 | cpdef bint is_line_2d(self) 248 | cpdef bint is_line_3d(self) 249 | cpdef bint is_line(self) 250 | cpdef bint is_cubic(self) 251 | cpdef bint is_circle(self) 252 | cpdef bint is_arc(self) 253 | 254 | 255 | cdef class SolverSystem: 256 | 257 | cdef Slvs_hGroup g 258 | cdef Slvs_System sys 259 | cdef cmap[Slvs_hParam, Slvs_Param] param_list 260 | cdef vector[Slvs_Entity] entity_list 261 | cdef vector[Slvs_Constraint] cons_list 262 | cdef vector[Slvs_hConstraint] failed_list 263 | 264 | cdef void copy_to_sys(self) nogil 265 | cdef void copy_from_sys(self) nogil 266 | cpdef void clear(self) 267 | cdef void failed_collecting(self) nogil 268 | cdef void free(self) 269 | cpdef void set_group(self, size_t g) 270 | cpdef int group(self) 271 | cpdef tuple params(self, Params p) 272 | cpdef int dof(self) 273 | cpdef object constraints(self) 274 | cpdef list faileds(self) 275 | cpdef int solve(self) 276 | cpdef Entity create_2d_base(self) 277 | cdef Slvs_hParam new_param(self, double val) nogil 278 | cdef Slvs_hEntity eh(self) nogil 279 | 280 | cpdef Entity add_point_2d(self, double u, double v, Entity wp) 281 | cpdef Entity add_point_3d(self, double x, double y, double z) 282 | cpdef Entity add_normal_2d(self, Entity wp) 283 | cpdef Entity add_normal_3d(self, double qw, double qx, double qy, double qz) 284 | cpdef Entity add_distance(self, double d, Entity wp) 285 | cpdef Entity add_line_2d(self, Entity p1, Entity p2, Entity wp) 286 | cpdef Entity add_line_3d(self, Entity p1, Entity p2) 287 | cpdef Entity add_cubic(self, Entity p1, Entity p2, Entity p3, Entity p4, Entity wp) 288 | cpdef Entity add_arc(self, Entity nm, Entity ct, Entity start, Entity end, Entity wp) 289 | cpdef Entity add_circle(self, Entity nm, Entity ct, Entity radius, Entity wp) 290 | cpdef Entity add_work_plane(self, Entity origin, Entity nm) 291 | cpdef void add_constraint( 292 | self, 293 | Constraint c_type, 294 | Entity wp, 295 | double v, 296 | Entity p1, 297 | Entity p2, 298 | Entity e1, 299 | Entity e2, 300 | Entity e3 = *, 301 | Entity e4 = *, 302 | int other = *, 303 | int other2 = * 304 | ) 305 | 306 | cpdef void coincident(self, Entity e1, Entity e2, Entity wp = *) 307 | cpdef void distance(self, Entity e1, Entity e2, double value, Entity wp = *) 308 | cpdef void equal(self, Entity e1, Entity e2, Entity wp = *) 309 | cpdef void equal_included_angle(self, Entity e1, Entity e2, Entity e3, Entity e4, Entity wp) 310 | cpdef void equal_point_to_line(self, Entity e1, Entity e2, Entity e3, Entity e4, Entity wp) 311 | cpdef void ratio(self, Entity e1, Entity e2, double value, Entity wp) 312 | cpdef void symmetric(self, Entity e1, Entity e2, Entity e3 = *, Entity wp = *) 313 | cpdef void symmetric_h(self, Entity e1, Entity e2, Entity wp) 314 | cpdef void symmetric_v(self, Entity e1, Entity e2, Entity wp) 315 | cpdef void midpoint(self, Entity e1, Entity e2, Entity wp = *) 316 | cpdef void horizontal(self, Entity e1, Entity wp) 317 | cpdef void vertical(self, Entity e1, Entity wp) 318 | cpdef void diameter(self, Entity e1, double value, Entity wp) 319 | cpdef void same_orientation(self, Entity e1, Entity e2) 320 | cpdef void angle(self, Entity e1, Entity e2, double value, Entity wp, bint inverse = *) 321 | cpdef void perpendicular(self, Entity e1, Entity e2, Entity wp, bint inverse = *) 322 | cpdef void parallel(self, Entity e1, Entity e2, Entity wp = *) 323 | cpdef void tangent(self, Entity e1, Entity e2, Entity wp = *) 324 | cpdef void distance_proj(self, Entity e1, Entity e2, double value) 325 | cpdef void dragged(self, Entity e1, Entity wp = *) 326 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Python-Solvespace Makefile 2 | 3 | # author: Yuan Chang 4 | # copyright: Copyright (C) 2016-2019 5 | # license: AGPL 6 | # email: pyslvs@gmail.com 7 | 8 | ifeq ($(OS), Windows_NT) 9 | SHELL = cmd 10 | endif 11 | 12 | .PHONY: build test clean 13 | 14 | all: build 15 | 16 | build: setup.py 17 | ifeq ($(OS),Windows_NT) 18 | python $< build_ext -j0 --inplace 19 | else 20 | python3 $< build_ext -j0 --inplace 21 | endif 22 | 23 | test: test_slvs.py build 24 | ifeq ($(OS),Windows_NT) 25 | python $< 26 | else 27 | python3 $< 28 | endif 29 | 30 | clean: 31 | ifeq ($(OS),Windows_NT) 32 | -rd build /s /q 33 | -del *.so 34 | -del *.pyd 35 | -del Cython\*.cpp 36 | else 37 | -rm -fr build 38 | -rm -f *.so 39 | -rm -f Cython/*.cpp 40 | endif 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/b2o8jw7xnfqghqr5?svg=true)](https://ci.appveyor.com/project/KmolYuan/python-solvespace) 2 | [![Build status](https://travis-ci.org/KmolYuan/python-solvespace.svg)](https://travis-ci.org/KmolYuan/python-solvespace) 3 | ![OS](https://img.shields.io/badge/OS-Windows%2C%20Mac%20OS%2C%20Ubuntu-blue.svg) 4 | [![GitHub license](https://img.shields.io/badge/license-AGPLv3-blue.svg)](https://raw.githubusercontent.com/KmolYuan/python-solvespace/master/LICENSE) 5 | 6 | Python Solvespace 7 | === 8 | 9 | Python library from solver of Solvespace. 10 | 11 | Use for academic research and learning. 12 | 13 | Feature for CDemo and Python interface can see [here](https://github.com/KmolYuan/python-solvespace/blob/master/Cython/DOC.txt). 14 | 15 | Requirement 16 | === 17 | 18 | 1. [GNU Make] (Windows) 19 | 20 | 1. [Cython] 21 | 22 | Build and Test 23 | === 24 | 25 | Build or clean the library: 26 | 27 | ```bash 28 | make 29 | make clean 30 | ``` 31 | 32 | Run unit test: 33 | 34 | ```bash 35 | python test_slvs.py 36 | ``` 37 | 38 | [GNU Make]: https://sourceforge.net/projects/mingw-w64/files/latest/download?source=files 39 | [Cython]: https://cython.org/ 40 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """'python_solvespace' module is a wrapper of 4 | Python binding Solvespace solver libraries. 5 | """ 6 | 7 | __author__ = "Yuan Chang" 8 | __copyright__ = "Copyright (C) 2016-2019" 9 | __license__ = "AGPL" 10 | __email__ = "pyslvs@gmail.com" 11 | 12 | from .slvs import ( 13 | quaternion_u, 14 | quaternion_v, 15 | quaternion_n, 16 | make_quaternion, 17 | Constraint, 18 | ResultFlag, 19 | Params, 20 | Entity, 21 | SolverSystem, 22 | ) 23 | 24 | __all__ = [ 25 | 'quaternion_u', 26 | 'quaternion_v', 27 | 'quaternion_n', 28 | 'make_quaternion', 29 | 'Constraint', 30 | 'ResultFlag', 31 | 'Params', 32 | 'Entity', 33 | 'SolverSystem', 34 | ] 35 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # AppVeyor for Pyslvs-UI 2 | image: Visual Studio 2017 3 | 4 | platform: 5 | - x64 6 | 7 | environment: 8 | matrix: 9 | 10 | - MINGW_DIR: &mingw C:\mingw-w64\x86_64-7.3.0-posix-seh-rt_v5-rev0 11 | MSYS_DIR: &msys2 C:\msys64 12 | PYTHON_DIR: C:\Python36-x64 13 | 14 | - MINGW_DIR: *mingw 15 | MSYS_DIR: *msys2 16 | PYTHON_DIR: C:\Python37-x64 17 | 18 | skip_tags: true 19 | 20 | install: 21 | # Environment variables. 22 | - set Path=%PYTHON_DIR%;%Path% 23 | - set Path=%PYTHON_DIR%\Scripts;%Path% 24 | - set Path=%MSYS_DIR%\usr\bin;%Path% 25 | - set Path=%MINGW_DIR%\mingw64\bin;%Path% 26 | 27 | # Show Python. 28 | - python --version 29 | - pip --version 30 | 31 | # Set Python compiler to Msys2. 32 | - pip install setuptools -U 33 | - echo [build]>> %PYTHON_DIR%\Lib\distutils\distutils.cfg 34 | - echo compiler = mingw32>> %PYTHON_DIR%\Lib\distutils\distutils.cfg 35 | - patch %PYTHON_DIR%\lib\distutils\cygwinccompiler.py platform\patch.diff 36 | - copy %PYTHON_DIR%\vcruntime140.dll %PYTHON_DIR%\libs 37 | 38 | # Install modules. 39 | - pip install -r requirements.txt 40 | 41 | # Show tool kits. 42 | - gcc --version 43 | - mingw32-make --version 44 | 45 | build_script: 46 | - mingw32-make 47 | 48 | test_script: 49 | - mingw32-make test 50 | -------------------------------------------------------------------------------- /example/example_crank_rocker.slvs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KmolYuan/python-solvespace/69cac65b6954e516ee3fa6254e7b646d5a4a1137/example/example_crank_rocker.slvs -------------------------------------------------------------------------------- /example/example_jansen_linkage.slvs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KmolYuan/python-solvespace/69cac65b6954e516ee3fa6254e7b646d5a4a1137/example/example_jansen_linkage.slvs -------------------------------------------------------------------------------- /example/example_nut_cracker.slvs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KmolYuan/python-solvespace/69cac65b6954e516ee3fa6254e7b646d5a4a1137/example/example_nut_cracker.slvs -------------------------------------------------------------------------------- /include/slvs.h: -------------------------------------------------------------------------------- 1 | /*----------------------------------------------------------------------------- 2 | * Data structures and prototypes for slvs.lib, a geometric constraint solver. 3 | * 4 | * See the comments in this file, the accompanying sample code that uses 5 | * this library, and the accompanying documentation (DOC.txt). 6 | * 7 | * Copyright 2009-2013 Jonathan Westhues. 8 | *---------------------------------------------------------------------------*/ 9 | 10 | #ifndef __SLVS_H 11 | #define __SLVS_H 12 | 13 | #ifdef WIN32 14 | # ifdef EXPORT_DLL 15 | # define DLL __declspec( dllexport ) 16 | # else 17 | # define DLL __declspec( dllimport ) 18 | # endif 19 | #else 20 | # define DLL 21 | #endif 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | #ifdef _MSC_VER 28 | typedef unsigned __int32 uint32_t; 29 | #else 30 | #include 31 | #endif 32 | 33 | typedef uint32_t Slvs_hParam; 34 | typedef uint32_t Slvs_hEntity; 35 | typedef uint32_t Slvs_hConstraint; 36 | typedef uint32_t Slvs_hGroup; 37 | 38 | /* To obtain the 3d (not projected into a workplane) of a constraint or 39 | * an entity, specify this instead of the workplane. */ 40 | #define SLVS_FREE_IN_3D 0 41 | 42 | 43 | typedef struct { 44 | Slvs_hParam h; 45 | Slvs_hGroup group; 46 | double val; 47 | } Slvs_Param; 48 | 49 | 50 | #define SLVS_E_POINT_IN_3D 50000 51 | #define SLVS_E_POINT_IN_2D 50001 52 | 53 | #define SLVS_E_NORMAL_IN_3D 60000 54 | #define SLVS_E_NORMAL_IN_2D 60001 55 | 56 | #define SLVS_E_DISTANCE 70000 57 | 58 | /* The special point, normal, and distance types used for parametric step 59 | * and repeat, extrude, and assembly are currently not exposed. Please 60 | * contact us if you are interested in using these. */ 61 | 62 | #define SLVS_E_WORKPLANE 80000 63 | #define SLVS_E_LINE_SEGMENT 80001 64 | #define SLVS_E_CUBIC 80002 65 | #define SLVS_E_CIRCLE 80003 66 | #define SLVS_E_ARC_OF_CIRCLE 80004 67 | 68 | typedef struct { 69 | Slvs_hEntity h; 70 | Slvs_hGroup group; 71 | 72 | int type; 73 | 74 | Slvs_hEntity wrkpl; 75 | Slvs_hEntity point[4]; 76 | Slvs_hEntity normal; 77 | Slvs_hEntity distance; 78 | 79 | Slvs_hParam param[4]; 80 | } Slvs_Entity; 81 | 82 | #define SLVS_C_POINTS_COINCIDENT 100000 83 | #define SLVS_C_PT_PT_DISTANCE 100001 84 | #define SLVS_C_PT_PLANE_DISTANCE 100002 85 | #define SLVS_C_PT_LINE_DISTANCE 100003 86 | #define SLVS_C_PT_FACE_DISTANCE 100004 87 | #define SLVS_C_PT_IN_PLANE 100005 88 | #define SLVS_C_PT_ON_LINE 100006 89 | #define SLVS_C_PT_ON_FACE 100007 90 | #define SLVS_C_EQUAL_LENGTH_LINES 100008 91 | #define SLVS_C_LENGTH_RATIO 100009 92 | #define SLVS_C_EQ_LEN_PT_LINE_D 100010 93 | #define SLVS_C_EQ_PT_LN_DISTANCES 100011 94 | #define SLVS_C_EQUAL_ANGLE 100012 95 | #define SLVS_C_EQUAL_LINE_ARC_LEN 100013 96 | #define SLVS_C_SYMMETRIC 100014 97 | #define SLVS_C_SYMMETRIC_HORIZ 100015 98 | #define SLVS_C_SYMMETRIC_VERT 100016 99 | #define SLVS_C_SYMMETRIC_LINE 100017 100 | #define SLVS_C_AT_MIDPOINT 100018 101 | #define SLVS_C_HORIZONTAL 100019 102 | #define SLVS_C_VERTICAL 100020 103 | #define SLVS_C_DIAMETER 100021 104 | #define SLVS_C_PT_ON_CIRCLE 100022 105 | #define SLVS_C_SAME_ORIENTATION 100023 106 | #define SLVS_C_ANGLE 100024 107 | #define SLVS_C_PARALLEL 100025 108 | #define SLVS_C_PERPENDICULAR 100026 109 | #define SLVS_C_ARC_LINE_TANGENT 100027 110 | #define SLVS_C_CUBIC_LINE_TANGENT 100028 111 | #define SLVS_C_EQUAL_RADIUS 100029 112 | #define SLVS_C_PROJ_PT_DISTANCE 100030 113 | #define SLVS_C_WHERE_DRAGGED 100031 114 | #define SLVS_C_CURVE_CURVE_TANGENT 100032 115 | #define SLVS_C_LENGTH_DIFFERENCE 100033 116 | 117 | typedef struct { 118 | Slvs_hConstraint h; 119 | Slvs_hGroup group; 120 | 121 | int type; 122 | 123 | Slvs_hEntity wrkpl; 124 | 125 | double valA; 126 | Slvs_hEntity ptA; 127 | Slvs_hEntity ptB; 128 | Slvs_hEntity entityA; 129 | Slvs_hEntity entityB; 130 | Slvs_hEntity entityC; 131 | Slvs_hEntity entityD; 132 | 133 | int other; 134 | int other2; 135 | } Slvs_Constraint; 136 | 137 | 138 | typedef struct { 139 | /*** INPUT VARIABLES 140 | * 141 | * Here, we specify the parameters and their initial values, the entities, 142 | * and the constraints. For example, param[] points to the array of 143 | * parameters, which has length params, so that the last valid element 144 | * is param[params-1]. 145 | * 146 | * param[] is actually an in/out variable; if the solver is successful, 147 | * then the new values (that satisfy the constraints) are written to it. */ 148 | Slvs_Param *param; 149 | int params; 150 | Slvs_Entity *entity; 151 | int entities; 152 | Slvs_Constraint *constraint; 153 | int constraints; 154 | 155 | /* If a parameter corresponds to a point (distance, normal, etc.) being 156 | * dragged, then specify it here. This will cause the solver to favor 157 | * that parameter, and attempt to change it as little as possible even 158 | * if that requires it to change other parameters more. 159 | * 160 | * Unused members of this array should be set to zero. */ 161 | Slvs_hParam dragged[4]; 162 | 163 | /* If the solver fails, then it can determine which constraints are 164 | * causing the problem. But this is a relatively slow process (for 165 | * a system with n constraints, about n times as long as just solving). 166 | * If calculateFaileds is true, then the solver will do so, otherwise 167 | * not. */ 168 | int calculateFaileds; 169 | 170 | /*** OUTPUT VARIABLES 171 | * 172 | * If the solver fails, then it can report which constraints are causing 173 | * the problem. The caller should allocate the array failed[], and pass 174 | * its size in faileds. 175 | * 176 | * The solver will set faileds equal to the number of problematic 177 | * constraints, and write their Slvs_hConstraints into failed[]. To 178 | * ensure that there is sufficient space for any possible set of 179 | * failing constraints, faileds should be greater than or equal to 180 | * constraints. */ 181 | Slvs_hConstraint *failed; 182 | int faileds; 183 | 184 | /* The solver indicates the number of unconstrained degrees of freedom. */ 185 | int dof; 186 | 187 | /* The solver indicates whether the solution succeeded. */ 188 | #define SLVS_RESULT_OKAY 0 189 | #define SLVS_RESULT_INCONSISTENT 1 190 | #define SLVS_RESULT_DIDNT_CONVERGE 2 191 | #define SLVS_RESULT_TOO_MANY_UNKNOWNS 3 192 | int result; 193 | } Slvs_System; 194 | 195 | DLL void Slvs_Solve(Slvs_System *sys, Slvs_hGroup hg); 196 | 197 | 198 | /* Our base coordinate system has basis vectors 199 | * (1, 0, 0) (0, 1, 0) (0, 0, 1) 200 | * A unit quaternion defines a rotation to a new coordinate system with 201 | * basis vectors 202 | * U V N 203 | * which these functions compute from the quaternion. */ 204 | DLL void Slvs_QuaternionU(double qw, double qx, double qy, double qz, 205 | double *x, double *y, double *z); 206 | DLL void Slvs_QuaternionV(double qw, double qx, double qy, double qz, 207 | double *x, double *y, double *z); 208 | DLL void Slvs_QuaternionN(double qw, double qx, double qy, double qz, 209 | double *x, double *y, double *z); 210 | 211 | /* Similarly, compute a unit quaternion in terms of two basis vectors. */ 212 | DLL void Slvs_MakeQuaternion(double ux, double uy, double uz, 213 | double vx, double vy, double vz, 214 | double *qw, double *qx, double *qy, double *qz); 215 | 216 | 217 | /*------------------------------------- 218 | * These are just convenience functions, to save you the trouble of filling 219 | * out the structures by hand. The code is included in the header file to 220 | * let the compiler inline them if possible. */ 221 | 222 | static inline Slvs_Param Slvs_MakeParam(Slvs_hParam h, Slvs_hGroup group, double val) 223 | { 224 | Slvs_Param r; 225 | r.h = h; 226 | r.group = group; 227 | r.val = val; 228 | return r; 229 | } 230 | static inline Slvs_Entity Slvs_MakePoint2d(Slvs_hEntity h, Slvs_hGroup group, 231 | Slvs_hEntity wrkpl, 232 | Slvs_hParam u, Slvs_hParam v) 233 | { 234 | Slvs_Entity r; 235 | memset(&r, 0, sizeof(r)); 236 | r.h = h; 237 | r.group = group; 238 | r.type = SLVS_E_POINT_IN_2D; 239 | r.wrkpl = wrkpl; 240 | r.param[0] = u; 241 | r.param[1] = v; 242 | return r; 243 | } 244 | static inline Slvs_Entity Slvs_MakePoint3d(Slvs_hEntity h, Slvs_hGroup group, 245 | Slvs_hParam x, Slvs_hParam y, Slvs_hParam z) 246 | { 247 | Slvs_Entity r; 248 | memset(&r, 0, sizeof(r)); 249 | r.h = h; 250 | r.group = group; 251 | r.type = SLVS_E_POINT_IN_3D; 252 | r.wrkpl = SLVS_FREE_IN_3D; 253 | r.param[0] = x; 254 | r.param[1] = y; 255 | r.param[2] = z; 256 | return r; 257 | } 258 | static inline Slvs_Entity Slvs_MakeNormal3d(Slvs_hEntity h, Slvs_hGroup group, 259 | Slvs_hParam qw, Slvs_hParam qx, 260 | Slvs_hParam qy, Slvs_hParam qz) 261 | { 262 | Slvs_Entity r; 263 | memset(&r, 0, sizeof(r)); 264 | r.h = h; 265 | r.group = group; 266 | r.type = SLVS_E_NORMAL_IN_3D; 267 | r.wrkpl = SLVS_FREE_IN_3D; 268 | r.param[0] = qw; 269 | r.param[1] = qx; 270 | r.param[2] = qy; 271 | r.param[3] = qz; 272 | return r; 273 | } 274 | static inline Slvs_Entity Slvs_MakeNormal2d(Slvs_hEntity h, Slvs_hGroup group, 275 | Slvs_hEntity wrkpl) 276 | { 277 | Slvs_Entity r; 278 | memset(&r, 0, sizeof(r)); 279 | r.h = h; 280 | r.group = group; 281 | r.type = SLVS_E_NORMAL_IN_2D; 282 | r.wrkpl = wrkpl; 283 | return r; 284 | } 285 | static inline Slvs_Entity Slvs_MakeDistance(Slvs_hEntity h, Slvs_hGroup group, 286 | Slvs_hEntity wrkpl, Slvs_hParam d) 287 | { 288 | Slvs_Entity r; 289 | memset(&r, 0, sizeof(r)); 290 | r.h = h; 291 | r.group = group; 292 | r.type = SLVS_E_DISTANCE; 293 | r.wrkpl = wrkpl; 294 | r.param[0] = d; 295 | return r; 296 | } 297 | static inline Slvs_Entity Slvs_MakeLineSegment(Slvs_hEntity h, Slvs_hGroup group, 298 | Slvs_hEntity wrkpl, 299 | Slvs_hEntity ptA, Slvs_hEntity ptB) 300 | { 301 | Slvs_Entity r; 302 | memset(&r, 0, sizeof(r)); 303 | r.h = h; 304 | r.group = group; 305 | r.type = SLVS_E_LINE_SEGMENT; 306 | r.wrkpl = wrkpl; 307 | r.point[0] = ptA; 308 | r.point[1] = ptB; 309 | return r; 310 | } 311 | static inline Slvs_Entity Slvs_MakeCubic(Slvs_hEntity h, Slvs_hGroup group, 312 | Slvs_hEntity wrkpl, 313 | Slvs_hEntity pt0, Slvs_hEntity pt1, 314 | Slvs_hEntity pt2, Slvs_hEntity pt3) 315 | { 316 | Slvs_Entity r; 317 | memset(&r, 0, sizeof(r)); 318 | r.h = h; 319 | r.group = group; 320 | r.type = SLVS_E_CUBIC; 321 | r.wrkpl = wrkpl; 322 | r.point[0] = pt0; 323 | r.point[1] = pt1; 324 | r.point[2] = pt2; 325 | r.point[3] = pt3; 326 | return r; 327 | } 328 | static inline Slvs_Entity Slvs_MakeArcOfCircle(Slvs_hEntity h, Slvs_hGroup group, 329 | Slvs_hEntity wrkpl, 330 | Slvs_hEntity normal, 331 | Slvs_hEntity center, 332 | Slvs_hEntity start, Slvs_hEntity end) 333 | { 334 | Slvs_Entity r; 335 | memset(&r, 0, sizeof(r)); 336 | r.h = h; 337 | r.group = group; 338 | r.type = SLVS_E_ARC_OF_CIRCLE; 339 | r.wrkpl = wrkpl; 340 | r.normal = normal; 341 | r.point[0] = center; 342 | r.point[1] = start; 343 | r.point[2] = end; 344 | return r; 345 | } 346 | static inline Slvs_Entity Slvs_MakeCircle(Slvs_hEntity h, Slvs_hGroup group, 347 | Slvs_hEntity wrkpl, 348 | Slvs_hEntity center, 349 | Slvs_hEntity normal, Slvs_hEntity radius) 350 | { 351 | Slvs_Entity r; 352 | memset(&r, 0, sizeof(r)); 353 | r.h = h; 354 | r.group = group; 355 | r.type = SLVS_E_CIRCLE; 356 | r.wrkpl = wrkpl; 357 | r.point[0] = center; 358 | r.normal = normal; 359 | r.distance = radius; 360 | return r; 361 | } 362 | static inline Slvs_Entity Slvs_MakeWorkplane(Slvs_hEntity h, Slvs_hGroup group, 363 | Slvs_hEntity origin, Slvs_hEntity normal) 364 | { 365 | Slvs_Entity r; 366 | memset(&r, 0, sizeof(r)); 367 | r.h = h; 368 | r.group = group; 369 | r.type = SLVS_E_WORKPLANE; 370 | r.wrkpl = SLVS_FREE_IN_3D; 371 | r.point[0] = origin; 372 | r.normal = normal; 373 | return r; 374 | } 375 | 376 | static inline Slvs_Constraint Slvs_MakeConstraint(Slvs_hConstraint h, 377 | Slvs_hGroup group, 378 | int type, 379 | Slvs_hEntity wrkpl, 380 | double valA, 381 | Slvs_hEntity ptA, 382 | Slvs_hEntity ptB, 383 | Slvs_hEntity entityA, 384 | Slvs_hEntity entityB) 385 | { 386 | Slvs_Constraint r; 387 | memset(&r, 0, sizeof(r)); 388 | r.h = h; 389 | r.group = group; 390 | r.type = type; 391 | r.wrkpl = wrkpl; 392 | r.valA = valA; 393 | r.ptA = ptA; 394 | r.ptB = ptB; 395 | r.entityA = entityA; 396 | r.entityB = entityB; 397 | return r; 398 | } 399 | 400 | #ifdef __cplusplus 401 | } 402 | #endif 403 | 404 | #endif 405 | -------------------------------------------------------------------------------- /platform/patch.diff: -------------------------------------------------------------------------------- 1 | --- cygwinccompiler.py 2 | +++ cygwinccompiler.py 3 | @@ -82,7 +82,25 @@ def get_msvcr(): 4 | elif msc_ver == '1600': 5 | # VS2010 / MSVC 10.0 6 | return ['msvcr100'] 7 | + elif msc_ver == '1700': 8 | + # Visual Studio 2012 / Visual C++ 11.0 9 | + return ['msvcr110'] 10 | + elif msc_ver == '1800': 11 | + # Visual Studio 2013 / Visual C++ 12.0 12 | + return ['msvcr120'] 13 | + elif msc_ver == '1900': 14 | + # Visual Studio 2015 / Visual C++ 14.0 15 | + # "msvcr140.dll no longer exists" http://blogs.msdn.com/b/vcblog/archive/2014/06/03/visual-studio-14-ctp.aspx 16 | + return ['vcruntime140'] 17 | + elif msc_ver == '1910': 18 | + return ['vcruntime140'] 19 | + elif msc_ver == '1914': 20 | + return ['vcruntime140'] 21 | + elif msc_ver == '1915': 22 | + return ['vcruntime140'] 23 | + elif msc_ver == '1916': 24 | + return ['vcruntime140'] 25 | else: 26 | raise ValueError("Unknown MS Compiler version %s " % msc_ver) 27 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | cython 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """Compile the Cython libraries of Python-Solvespace.""" 4 | 5 | __author__ = "Yuan Chang" 6 | __copyright__ = "Copyright (C) 2016-2019" 7 | __license__ = "AGPL" 8 | __email__ = "pyslvs@gmail.com" 9 | 10 | import os 11 | import codecs 12 | from setuptools import setup, Extension, find_packages 13 | from platform import system 14 | from distutils import sysconfig 15 | 16 | here = os.path.abspath(os.path.dirname(__file__)) 17 | src_path = 'src/' 18 | platform_path = src_path + 'platform/' 19 | ver = sysconfig.get_config_var('VERSION') 20 | lib = sysconfig.get_config_var('BINDIR') 21 | 22 | 23 | def read(*parts): 24 | with codecs.open(os.path.join(here, *parts), 'r') as f: 25 | return f.read() 26 | 27 | 28 | macros = [ 29 | ('_hypot', 'hypot'), 30 | ('M_PI', 'PI'), # C++ 11 31 | ('ISOLATION_AWARE_ENABLED', None), 32 | ('LIBRARY', None), 33 | ('EXPORT_DLL', None), 34 | ('_CRT_SECURE_NO_WARNINGS', None), 35 | ] 36 | 37 | compile_args = [ 38 | '-O3', 39 | '-Wno-cpp', 40 | '-g', 41 | '-Wno-write-strings', 42 | '-fpermissive', 43 | '-fPIC', 44 | '-std=c++11', 45 | ] 46 | 47 | sources = [ 48 | 'Cython/' + 'slvs.pyx', 49 | src_path + 'util.cpp', 50 | src_path + 'entity.cpp', 51 | src_path + 'expr.cpp', 52 | src_path + 'constrainteq.cpp', 53 | src_path + 'constraint.cpp', 54 | src_path + 'system.cpp', 55 | src_path + 'lib.cpp', 56 | ] 57 | 58 | if system() == 'Windows': 59 | # Avoid compile error with CYTHON_USE_PYLONG_INTERNALS. 60 | # https://github.com/cython/cython/issues/2670#issuecomment-432212671 61 | macros.append(('MS_WIN64', None)) 62 | # Disable format warning 63 | compile_args.append('-Wno-format') 64 | 65 | # Solvespace arguments 66 | macros.append(('WIN32', None)) 67 | 68 | # Platform sources 69 | sources.append(platform_path + 'w32util.cpp') 70 | sources.append(platform_path + 'platform.cpp') 71 | else: 72 | sources.append(platform_path + 'unixutil.cpp') 73 | 74 | setup( 75 | name="python_solvespace", 76 | # version="3.0.0", 77 | author=__author__, 78 | author_email=__email__, 79 | description="Python library of Solvespace", 80 | long_description=read("README.md"), 81 | url="https://github.com/solvespace/solvespace", 82 | packages=find_packages(), 83 | ext_modules=[Extension( 84 | "slvs", 85 | sources, 86 | language="c++", 87 | include_dirs=['include', src_path, platform_path], 88 | define_macros=macros, 89 | extra_compile_args=compile_args 90 | )], 91 | python_requires=">=3.6", 92 | setup_requires=[ 93 | 'setuptools', 94 | 'wheel', 95 | 'cython', 96 | ], 97 | classifiers=[ 98 | "Programming Language :: Python :: 3", 99 | "Programming Language :: Cython", 100 | "License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)", 101 | "Programming Language :: Python :: 3.6", 102 | "Programming Language :: Python :: 3.7", 103 | "Operating System :: OS Independent", 104 | ] 105 | ) 106 | -------------------------------------------------------------------------------- /slvs.pyi: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from typing import Tuple, List, Counter 4 | from enum import IntEnum, auto 5 | 6 | 7 | def quaternion_u( 8 | qw: float, 9 | qx: float, 10 | qy: float, 11 | qz: float 12 | ) -> Tuple[float, float, float]: 13 | ... 14 | 15 | def quaternion_v( 16 | qw: float, 17 | qx: float, 18 | qy: float, 19 | qz: float 20 | ) -> Tuple[float, float, float]: 21 | ... 22 | 23 | def quaternion_n( 24 | qw: float, 25 | qx: float, 26 | qy: float, 27 | qz: float 28 | ) -> Tuple[float, float, float]: 29 | ... 30 | 31 | def make_quaternion( 32 | ux: float, 33 | uy: float, 34 | uz: float, 35 | vx: float, 36 | vy: float, 37 | vz: float 38 | ) -> Tuple[float, float, float, float]: 39 | ... 40 | 41 | 42 | class Constraint(IntEnum): 43 | # Expose macro of constraint types 44 | POINTS_COINCIDENT = 100000 45 | PT_PT_DISTANCE = auto() 46 | PT_PLANE_DISTANCE = auto() 47 | PT_LINE_DISTANCE = auto() 48 | PT_FACE_DISTANCE = auto() 49 | PT_IN_PLANE = auto() 50 | PT_ON_LINE = auto() 51 | PT_ON_FACE = auto() 52 | EQUAL_LENGTH_LINES = auto() 53 | LENGTH_RATIO = auto() 54 | EQ_LEN_PT_LINE_D = auto() 55 | EQ_PT_LN_DISTANCES = auto() 56 | EQUAL_ANGLE = auto() 57 | EQUAL_LINE_ARC_LEN = auto() 58 | SYMMETRIC = auto() 59 | SYMMETRIC_HORIZ = auto() 60 | SYMMETRIC_VERT = auto() 61 | SYMMETRIC_LINE = auto() 62 | AT_MIDPOINT = auto() 63 | HORIZONTAL = auto() 64 | VERTICAL = auto() 65 | DIAMETER = auto() 66 | PT_ON_CIRCLE = auto() 67 | SAME_ORIENTATION = auto() 68 | ANGLE = auto() 69 | PARALLEL = auto() 70 | PERPENDICULAR = auto() 71 | ARC_LINE_TANGENT = auto() 72 | CUBIC_LINE_TANGENT = auto() 73 | EQUAL_RADIUS = auto() 74 | PROJ_PT_DISTANCE = auto() 75 | WHERE_DRAGGED = auto() 76 | CURVE_CURVE_TANGENT = auto() 77 | LENGTH_DIFFERENCE = auto() 78 | 79 | 80 | class ResultFlag(IntEnum): 81 | # Expose macro of result flags 82 | OKAY = 0 83 | INCONSISTENT = auto() 84 | DIDNT_CONVERGE = auto() 85 | TOO_MANY_UNKNOWNS = auto() 86 | 87 | 88 | class Params: 89 | 90 | def __repr__(self) -> str: 91 | ... 92 | 93 | 94 | class Entity: 95 | 96 | FREE_IN_3D: Entity 97 | NONE: Entity 98 | 99 | params: Params 100 | 101 | def is_3d(self) -> bool: 102 | ... 103 | 104 | def is_none(self) -> bool: 105 | ... 106 | 107 | def is_point_2d(self) -> bool: 108 | ... 109 | 110 | def is_point_3d(self) -> bool: 111 | ... 112 | 113 | def is_point(self) -> bool: 114 | ... 115 | 116 | def is_normal_2d(self) -> bool: 117 | ... 118 | 119 | def is_normal_3d(self) -> bool: 120 | ... 121 | 122 | def is_normal(self) -> bool: 123 | ... 124 | 125 | def is_distance(self) -> bool: 126 | ... 127 | 128 | def is_work_plane(self) -> bool: 129 | ... 130 | 131 | def is_line_2d(self) -> bool: 132 | ... 133 | 134 | def is_line_3d(self) -> bool: 135 | ... 136 | 137 | def is_line(self) -> bool: 138 | ... 139 | 140 | def is_cubic(self) -> bool: 141 | ... 142 | 143 | def is_circle(self) -> bool: 144 | ... 145 | 146 | def is_arc(self) -> bool: 147 | ... 148 | 149 | def __repr__(self) -> str: 150 | ... 151 | 152 | 153 | class SolverSystem: 154 | 155 | def __init__(self): 156 | ... 157 | 158 | def clear(self) -> None: 159 | ... 160 | 161 | def set_group(self, g: int) -> None: 162 | ... 163 | 164 | def group(self) -> int: 165 | ... 166 | 167 | def params(self, p: Params) -> Tuple[float, ...]: 168 | ... 169 | 170 | def dof(self) -> int: 171 | ... 172 | 173 | def constraints(self) -> Counter[str]: 174 | ... 175 | 176 | def faileds(self) -> List[int]: 177 | ... 178 | 179 | def solve(self) -> ResultFlag: 180 | ... 181 | 182 | def create_2d_base(self) -> Entity: 183 | ... 184 | 185 | def add_point_2d(self, u: float, v: float, wp: Entity) -> Entity: 186 | ... 187 | 188 | def add_point_3d(self, x: float, y: float, z: float) -> Entity: 189 | ... 190 | 191 | def add_normal_2d(self, wp: Entity) -> Entity: 192 | ... 193 | 194 | def add_normal_3d(self, qw: float, qx: float, qy: float, qz: float) -> Entity: 195 | ... 196 | 197 | def add_distance(self, d: float, wp: Entity) -> Entity: 198 | ... 199 | 200 | def add_line_2d(self, p1: Entity, p2: Entity, wp: Entity) -> Entity: 201 | ... 202 | 203 | def add_line_3d(self, p1: Entity, p2: Entity) -> Entity: 204 | ... 205 | 206 | def add_cubic(self, p1: Entity, p2: Entity, p3: Entity, p4: Entity, wp: Entity) -> Entity: 207 | ... 208 | 209 | def add_arc(self, nm: Entity, ct: Entity, start: Entity, end: Entity, wp: Entity) -> Entity: 210 | ... 211 | 212 | def add_circle(self, nm: Entity, ct: Entity, radius: Entity, wp: Entity) -> Entity: 213 | ... 214 | 215 | def add_work_plane(self, origin: Entity, nm: Entity) -> Entity: 216 | ... 217 | 218 | def add_constraint( 219 | self, 220 | c_type: Constraint, 221 | wp: Entity, 222 | v: float, 223 | p1: Entity, 224 | p2: Entity, 225 | e1: Entity, 226 | e2: Entity, 227 | e3: Entity = Entity.NONE, 228 | e4: Entity = Entity.NONE, 229 | other: int = 0, 230 | other2: int = 0 231 | ) -> None: 232 | ... 233 | 234 | def coincident(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: 235 | """Coincident two entities.""" 236 | ... 237 | 238 | def distance( 239 | self, 240 | e1: Entity, 241 | e2: Entity, 242 | value: float, 243 | wp: Entity = Entity.FREE_IN_3D 244 | ) -> None: 245 | """Distance constraint between two entities.""" 246 | ... 247 | 248 | def equal(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: 249 | """Equal constraint between two entities.""" 250 | ... 251 | 252 | def equal_included_angle( 253 | self, 254 | e1: Entity, 255 | e2: Entity, 256 | e3: Entity, 257 | e4: Entity, 258 | wp: Entity 259 | ) -> None: 260 | """Constraint that point 1 and line 1, point 2 and line 2 261 | must have same distance. 262 | """ 263 | ... 264 | 265 | def equal_point_to_line( 266 | self, 267 | e1: Entity, 268 | e2: Entity, 269 | e3: Entity, 270 | e4: Entity, 271 | wp: Entity 272 | ) -> None: 273 | """Constraint that line 1 and line 2, line 3 and line 4 274 | must have same included angle. 275 | """ 276 | ... 277 | 278 | def ratio(self, e1: Entity, e2: Entity, value: float, wp: Entity) -> None: 279 | """The ratio constraint between two lines.""" 280 | ... 281 | 282 | def symmetric( 283 | self, 284 | e1: Entity, 285 | e2: Entity, 286 | e3: Entity = Entity.NONE, 287 | wp: Entity = Entity.FREE_IN_3D 288 | ) -> None: 289 | """Symmetric constraint between two points.""" 290 | ... 291 | 292 | def symmetric_h(self, e1: Entity, e2: Entity, wp: Entity) -> None: 293 | """Symmetric constraint between two points with horizontal line.""" 294 | ... 295 | 296 | def symmetric_v(self, e1: Entity, e2: Entity, wp: Entity) -> None: 297 | """Symmetric constraint between two points with vertical line.""" 298 | ... 299 | 300 | def midpoint( 301 | self, 302 | e1: Entity, 303 | e2: Entity, 304 | wp: Entity = Entity.FREE_IN_3D 305 | ) -> None: 306 | """Midpoint constraint between a point and a line.""" 307 | ... 308 | 309 | def horizontal(self, e1: Entity, wp: Entity) -> None: 310 | """Horizontal constraint of a 2d point.""" 311 | ... 312 | 313 | def vertical(self, e1: Entity, wp: Entity) -> None: 314 | """Vertical constraint of a 2d point.""" 315 | ... 316 | 317 | def diameter(self, e1: Entity, value: float, wp: Entity) -> None: 318 | """Diameter constraint of a circular entities.""" 319 | ... 320 | 321 | def same_orientation(self, e1: Entity, e2: Entity) -> None: 322 | """Equal orientation constraint between two 3d normals.""" 323 | ... 324 | 325 | def angle(self, e1: Entity, e2: Entity, value: float, wp: Entity, inverse: bool = False) -> None: 326 | """Degrees angle constraint between two 2d lines.""" 327 | ... 328 | 329 | def perpendicular(self, e1: Entity, e2: Entity, wp: Entity, inverse: bool = False) -> None: 330 | """Perpendicular constraint between two 2d lines.""" 331 | ... 332 | 333 | def parallel(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: 334 | """Parallel constraint between two lines.""" 335 | ... 336 | 337 | def tangent(self, e1: Entity, e2: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: 338 | """Parallel constraint between two entities.""" 339 | ... 340 | 341 | def distance_proj(self, e1: Entity, e2: Entity, value: float) -> None: 342 | """Projected distance constraint between two 3d points.""" 343 | ... 344 | 345 | def dragged(self, e1: Entity, wp: Entity = Entity.FREE_IN_3D) -> None: 346 | """Dragged constraint of a point.""" 347 | ... 348 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef __CONFIG_H 2 | #define __CONFIG_H 3 | 4 | /* Non-OS X *nix only */ 5 | #define UNIX_DATADIR "C:/solvespace" 6 | 7 | #endif 8 | -------------------------------------------------------------------------------- /src/dsc.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Data structures used frequently in the program, various kinds of vectors 3 | // (of real numbers, not symbolic algebra stuff) and our templated lists. 4 | // 5 | // Copyright 2008-2013 Jonathan Westhues. 6 | //----------------------------------------------------------------------------- 7 | #ifndef __DSC_H 8 | #define __DSC_H 9 | 10 | #include "solvespace.h" 11 | 12 | class Vector; 13 | class Vector4; 14 | class Point2d; 15 | class hEntity; 16 | class hParam; 17 | 18 | class Quaternion { 19 | public: 20 | // a + (vx)*i + (vy)*j + (vz)*k 21 | double w, vx, vy, vz; 22 | 23 | static const Quaternion IDENTITY; 24 | 25 | static Quaternion From(double w, double vx, double vy, double vz); 26 | static Quaternion From(hParam w, hParam vx, hParam vy, hParam vz); 27 | static Quaternion From(Vector u, Vector v); 28 | static Quaternion From(Vector axis, double dtheta); 29 | 30 | Quaternion Plus(Quaternion b) const; 31 | Quaternion Minus(Quaternion b) const; 32 | Quaternion ScaledBy(double s) const; 33 | double Magnitude() const; 34 | Quaternion WithMagnitude(double s) const; 35 | 36 | // Call a rotation matrix [ u' v' n' ]'; this returns the first and 37 | // second rows, where that matrix is generated by this quaternion 38 | Vector RotationU() const; 39 | Vector RotationV() const; 40 | Vector RotationN() const; 41 | Vector Rotate(Vector p) const; 42 | 43 | Quaternion ToThe(double p) const; 44 | Quaternion Inverse() const; 45 | Quaternion Times(Quaternion b) const; 46 | Quaternion Mirror() const; 47 | }; 48 | 49 | class Vector { 50 | public: 51 | double x, y, z; 52 | 53 | static Vector From(double x, double y, double z); 54 | static Vector From(hParam x, hParam y, hParam z); 55 | static Vector AtIntersectionOfPlanes(Vector n1, double d1, 56 | Vector n2, double d2); 57 | static Vector AtIntersectionOfLines(Vector a0, Vector a1, 58 | Vector b0, Vector b1, 59 | bool *skew, 60 | double *pa=NULL, double *pb=NULL); 61 | static Vector AtIntersectionOfPlaneAndLine(Vector n, double d, 62 | Vector p0, Vector p1, 63 | bool *parallel); 64 | static Vector AtIntersectionOfPlanes(Vector na, double da, 65 | Vector nb, double db, 66 | Vector nc, double dc, bool *parallel); 67 | static void ClosestPointBetweenLines(Vector pa, Vector da, 68 | Vector pb, Vector db, 69 | double *ta, double *tb); 70 | 71 | double Element(int i) const; 72 | bool Equals(Vector v, double tol=LENGTH_EPS) const; 73 | bool EqualsExactly(Vector v) const; 74 | Vector Plus(Vector b) const; 75 | Vector Minus(Vector b) const; 76 | Vector Negated() const; 77 | Vector Cross(Vector b) const; 78 | double DirectionCosineWith(Vector b) const; 79 | double Dot(Vector b) const; 80 | Vector Normal(int which) const; 81 | Vector RotatedAbout(Vector orig, Vector axis, double theta) const; 82 | Vector RotatedAbout(Vector axis, double theta) const; 83 | Vector DotInToCsys(Vector u, Vector v, Vector n) const; 84 | Vector ScaleOutOfCsys(Vector u, Vector v, Vector n) const; 85 | double DistanceToLine(Vector p0, Vector dp) const; 86 | double DistanceToPlane(Vector normal, Vector origin) const; 87 | bool OnLineSegment(Vector a, Vector b, double tol=LENGTH_EPS) const; 88 | Vector ClosestPointOnLine(Vector p0, Vector deltal) const; 89 | double Magnitude() const; 90 | double MagSquared() const; 91 | Vector WithMagnitude(double s) const; 92 | Vector ScaledBy(double s) const; 93 | Vector ProjectInto(hEntity wrkpl) const; 94 | Vector ProjectVectorInto(hEntity wrkpl) const; 95 | double DivPivoting(Vector delta) const; 96 | Vector ClosestOrtho() const; 97 | void MakeMaxMin(Vector *maxv, Vector *minv) const; 98 | Vector ClampWithin(double minv, double maxv) const; 99 | static bool BoundingBoxesDisjoint(Vector amax, Vector amin, 100 | Vector bmax, Vector bmin); 101 | static bool BoundingBoxIntersectsLine(Vector amax, Vector amin, 102 | Vector p0, Vector p1, bool asSegment); 103 | bool OutsideAndNotOn(Vector maxv, Vector minv) const; 104 | Vector InPerspective(Vector u, Vector v, Vector n, 105 | Vector origin, double cameraTan) const; 106 | Point2d Project2d(Vector u, Vector v) const; 107 | Point2d ProjectXy() const; 108 | Vector4 Project4d() const; 109 | }; 110 | 111 | struct VectorHash { 112 | size_t operator()(const Vector &v) const; 113 | }; 114 | 115 | struct VectorPred { 116 | bool operator()(Vector a, Vector b) const; 117 | }; 118 | 119 | class Vector4 { 120 | public: 121 | double w, x, y, z; 122 | 123 | static Vector4 From(double w, double x, double y, double z); 124 | static Vector4 From(double w, Vector v3); 125 | static Vector4 Blend(Vector4 a, Vector4 b, double t); 126 | 127 | Vector4 Plus(Vector4 b) const; 128 | Vector4 Minus(Vector4 b) const; 129 | Vector4 ScaledBy(double s) const; 130 | Vector PerspectiveProject() const; 131 | }; 132 | 133 | class Point2d { 134 | public: 135 | double x, y; 136 | 137 | static Point2d From(double x, double y); 138 | static Point2d FromPolar(double r, double a); 139 | 140 | Point2d Plus(const Point2d &b) const; 141 | Point2d Minus(const Point2d &b) const; 142 | Point2d ScaledBy(double s) const; 143 | double DivPivoting(Point2d delta) const; 144 | double Dot(Point2d p) const; 145 | double DistanceTo(const Point2d &p) const; 146 | double DistanceToLine(const Point2d &p0, const Point2d &dp, bool asSegment) const; 147 | double DistanceToLineSigned(const Point2d &p0, const Point2d &dp, bool asSegment) const; 148 | double Angle() const; 149 | double AngleTo(const Point2d &p) const; 150 | double Magnitude() const; 151 | double MagSquared() const; 152 | Point2d WithMagnitude(double v) const; 153 | Point2d Normal() const; 154 | bool Equals(Point2d v, double tol=LENGTH_EPS) const; 155 | }; 156 | 157 | // A simple list 158 | template 159 | class List { 160 | public: 161 | T *elem; 162 | int n; 163 | int elemsAllocated; 164 | 165 | void ReserveMore(int howMuch) { 166 | if(n + howMuch > elemsAllocated) { 167 | elemsAllocated = n + howMuch; 168 | T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(elem[0])); 169 | for(int i = 0; i < n; i++) { 170 | new(&newElem[i]) T(std::move(elem[i])); 171 | elem[i].~T(); 172 | } 173 | MemFree(elem); 174 | elem = newElem; 175 | } 176 | } 177 | 178 | void AllocForOneMore() { 179 | if(n >= elemsAllocated) { 180 | ReserveMore((elemsAllocated + 32)*2 - n); 181 | } 182 | } 183 | 184 | void Add(const T *t) { 185 | AllocForOneMore(); 186 | new(&elem[n++]) T(*t); 187 | } 188 | 189 | void AddToBeginning(const T *t) { 190 | AllocForOneMore(); 191 | new(&elem[n]) T(); 192 | std::move_backward(elem, elem + 1, elem + n + 1); 193 | elem[0] = *t; 194 | n++; 195 | } 196 | 197 | T *First() { 198 | return (n == 0) ? NULL : &(elem[0]); 199 | } 200 | const T *First() const { 201 | return (n == 0) ? NULL : &(elem[0]); 202 | } 203 | T *NextAfter(T *prev) { 204 | if(!prev) return NULL; 205 | if(prev - elem == (n - 1)) return NULL; 206 | return prev + 1; 207 | } 208 | const T *NextAfter(const T *prev) const { 209 | if(!prev) return NULL; 210 | if(prev - elem == (n - 1)) return NULL; 211 | return prev + 1; 212 | } 213 | 214 | T *begin() { return &elem[0]; } 215 | T *end() { return &elem[n]; } 216 | const T *begin() const { return &elem[0]; } 217 | const T *end() const { return &elem[n]; } 218 | 219 | void ClearTags() { 220 | int i; 221 | for(i = 0; i < n; i++) { 222 | elem[i].tag = 0; 223 | } 224 | } 225 | 226 | void Clear() { 227 | for(int i = 0; i < n; i++) 228 | elem[i].~T(); 229 | if(elem) MemFree(elem); 230 | elem = NULL; 231 | n = elemsAllocated = 0; 232 | } 233 | 234 | void RemoveTagged() { 235 | int src, dest; 236 | dest = 0; 237 | for(src = 0; src < n; src++) { 238 | if(elem[src].tag) { 239 | // this item should be deleted 240 | } else { 241 | if(src != dest) { 242 | elem[dest] = elem[src]; 243 | } 244 | dest++; 245 | } 246 | } 247 | for(int i = dest; i < n; i++) 248 | elem[i].~T(); 249 | n = dest; 250 | // and elemsAllocated is untouched, because we didn't resize 251 | } 252 | 253 | void RemoveLast(int cnt) { 254 | ssassert(n >= cnt, "Removing more elements than the list contains"); 255 | for(int i = n - cnt; i < n; i++) 256 | elem[i].~T(); 257 | n -= cnt; 258 | // and elemsAllocated is untouched, same as in RemoveTagged 259 | } 260 | 261 | void Reverse() { 262 | int i; 263 | for(i = 0; i < (n/2); i++) { 264 | swap(elem[i], elem[(n-1)-i]); 265 | } 266 | } 267 | }; 268 | 269 | // A list, where each element has an integer identifier. The list is kept 270 | // sorted by that identifier, and items can be looked up in log n time by 271 | // id. 272 | template 273 | class IdList { 274 | public: 275 | T *elem; 276 | int n; 277 | int elemsAllocated; 278 | 279 | uint32_t MaximumId() { 280 | if(n == 0) { 281 | return 0; 282 | } else { 283 | return elem[n - 1].h.v; 284 | } 285 | } 286 | 287 | H AddAndAssignId(T *t) { 288 | t->h.v = (MaximumId() + 1); 289 | Add(t); 290 | 291 | return t->h; 292 | } 293 | 294 | void ReserveMore(int howMuch) { 295 | if(n + howMuch > elemsAllocated) { 296 | elemsAllocated = n + howMuch; 297 | T *newElem = (T *)MemAlloc((size_t)elemsAllocated*sizeof(elem[0])); 298 | for(int i = 0; i < n; i++) { 299 | new(&newElem[i]) T(std::move(elem[i])); 300 | elem[i].~T(); 301 | } 302 | MemFree(elem); 303 | elem = newElem; 304 | } 305 | } 306 | 307 | void Add(T *t) { 308 | if(n >= elemsAllocated) { 309 | ReserveMore((elemsAllocated + 32)*2 - n); 310 | } 311 | 312 | int first = 0, last = n; 313 | // We know that we must insert within the closed interval [first,last] 314 | while(first != last) { 315 | int mid = (first + last)/2; 316 | H hm = elem[mid].h; 317 | ssassert(hm.v != t->h.v, "Handle isn't unique"); 318 | if(hm.v > t->h.v) { 319 | last = mid; 320 | } else if(hm.v < t->h.v) { 321 | first = mid + 1; 322 | } 323 | } 324 | 325 | int i = first; 326 | new(&elem[n]) T(); 327 | std::move_backward(elem + i, elem + n, elem + n + 1); 328 | elem[i] = *t; 329 | n++; 330 | } 331 | 332 | T *FindById(H h) { 333 | T *t = FindByIdNoOops(h); 334 | ssassert(t != NULL, "Cannot find handle"); 335 | return t; 336 | } 337 | 338 | int IndexOf(H h) { 339 | int first = 0, last = n-1; 340 | while(first <= last) { 341 | int mid = (first + last)/2; 342 | H hm = elem[mid].h; 343 | if(hm.v > h.v) { 344 | last = mid-1; // and first stays the same 345 | } else if(hm.v < h.v) { 346 | first = mid+1; // and last stays the same 347 | } else { 348 | return mid; 349 | } 350 | } 351 | return -1; 352 | } 353 | 354 | T *FindByIdNoOops(H h) { 355 | int first = 0, last = n-1; 356 | while(first <= last) { 357 | int mid = (first + last)/2; 358 | H hm = elem[mid].h; 359 | if(hm.v > h.v) { 360 | last = mid-1; // and first stays the same 361 | } else if(hm.v < h.v) { 362 | first = mid+1; // and last stays the same 363 | } else { 364 | return &(elem[mid]); 365 | } 366 | } 367 | return NULL; 368 | } 369 | 370 | T *First() { 371 | return (n == 0) ? NULL : &(elem[0]); 372 | } 373 | T *NextAfter(T *prev) { 374 | if(!prev) return NULL; 375 | if(prev - elem == (n - 1)) return NULL; 376 | return prev + 1; 377 | } 378 | 379 | T *begin() { return &elem[0]; } 380 | T *end() { return &elem[n]; } 381 | const T *begin() const { return &elem[0]; } 382 | const T *end() const { return &elem[n]; } 383 | 384 | void ClearTags() { 385 | int i; 386 | for(i = 0; i < n; i++) { 387 | elem[i].tag = 0; 388 | } 389 | } 390 | 391 | void Tag(H h, int tag) { 392 | int i; 393 | for(i = 0; i < n; i++) { 394 | if(elem[i].h.v == h.v) { 395 | elem[i].tag = tag; 396 | } 397 | } 398 | } 399 | 400 | void RemoveTagged() { 401 | int src, dest; 402 | dest = 0; 403 | for(src = 0; src < n; src++) { 404 | if(elem[src].tag) { 405 | // this item should be deleted 406 | elem[src].Clear(); 407 | } else { 408 | if(src != dest) { 409 | elem[dest] = elem[src]; 410 | } 411 | dest++; 412 | } 413 | } 414 | for(int i = dest; i < n; i++) 415 | elem[i].~T(); 416 | n = dest; 417 | // and elemsAllocated is untouched, because we didn't resize 418 | } 419 | void RemoveById(H h) { 420 | ClearTags(); 421 | FindById(h)->tag = 1; 422 | RemoveTagged(); 423 | } 424 | 425 | void MoveSelfInto(IdList *l) { 426 | l->Clear(); 427 | *l = *this; 428 | elemsAllocated = n = 0; 429 | elem = NULL; 430 | } 431 | 432 | void DeepCopyInto(IdList *l) { 433 | l->Clear(); 434 | l->elem = (T *)MemAlloc(elemsAllocated * sizeof(elem[0])); 435 | for(int i = 0; i < n; i++) 436 | new(&l->elem[i]) T(elem[i]); 437 | l->elemsAllocated = elemsAllocated; 438 | l->n = n; 439 | } 440 | 441 | void Clear() { 442 | for(int i = 0; i < n; i++) { 443 | elem[i].Clear(); 444 | elem[i].~T(); 445 | } 446 | elemsAllocated = n = 0; 447 | if(elem) MemFree(elem); 448 | elem = NULL; 449 | } 450 | 451 | }; 452 | 453 | class BandedMatrix { 454 | public: 455 | enum { 456 | MAX_UNKNOWNS = 16, 457 | RIGHT_OF_DIAG = 1, 458 | LEFT_OF_DIAG = 2 459 | }; 460 | 461 | double A[MAX_UNKNOWNS][MAX_UNKNOWNS]; 462 | double B[MAX_UNKNOWNS]; 463 | double X[MAX_UNKNOWNS]; 464 | int n; 465 | 466 | void Solve(); 467 | }; 468 | 469 | #define RGBi(r, g, b) RgbaColor::From((r), (g), (b)) 470 | #define RGBf(r, g, b) RgbaColor::FromFloat((float)(r), (float)(g), (float)(b)) 471 | 472 | // Note: sizeof(class RgbaColor) should be exactly 4 473 | // 474 | class RgbaColor { 475 | public: 476 | uint8_t red, green, blue, alpha; 477 | 478 | float redF() const { return (float)red / 255.0f; } 479 | float greenF() const { return (float)green / 255.0f; } 480 | float blueF() const { return (float)blue / 255.0f; } 481 | float alphaF() const { return (float)alpha / 255.0f; } 482 | 483 | bool IsEmpty() const { return alpha == 0; } 484 | 485 | bool Equals(RgbaColor c) const { 486 | return 487 | c.red == red && 488 | c.green == green && 489 | c.blue == blue && 490 | c.alpha == alpha; 491 | } 492 | 493 | RgbaColor WithAlpha(uint8_t newAlpha) const { 494 | RgbaColor color = *this; 495 | color.alpha = newAlpha; 496 | return color; 497 | } 498 | 499 | uint32_t ToPackedIntBGRA() const { 500 | return 501 | blue | 502 | (uint32_t)(green << 8) | 503 | (uint32_t)(red << 16) | 504 | (uint32_t)((255 - alpha) << 24); 505 | } 506 | 507 | uint32_t ToPackedInt() const { 508 | return 509 | red | 510 | (uint32_t)(green << 8) | 511 | (uint32_t)(blue << 16) | 512 | (uint32_t)((255 - alpha) << 24); 513 | } 514 | 515 | uint32_t ToARGB32() const { 516 | return 517 | blue | 518 | (uint32_t)(green << 8) | 519 | (uint32_t)(red << 16) | 520 | (uint32_t)(alpha << 24); 521 | } 522 | 523 | static RgbaColor From(int r, int g, int b, int a = 255) { 524 | RgbaColor c; 525 | c.red = (uint8_t)r; 526 | c.green = (uint8_t)g; 527 | c.blue = (uint8_t)b; 528 | c.alpha = (uint8_t)a; 529 | return c; 530 | } 531 | 532 | static RgbaColor FromFloat(float r, float g, float b, float a = 1.0) { 533 | return From( 534 | (int)(255.1f * r), 535 | (int)(255.1f * g), 536 | (int)(255.1f * b), 537 | (int)(255.1f * a)); 538 | } 539 | 540 | static RgbaColor FromPackedInt(uint32_t rgba) { 541 | return From( 542 | (int)((rgba) & 0xff), 543 | (int)((rgba >> 8) & 0xff), 544 | (int)((rgba >> 16) & 0xff), 545 | (int)(255 - ((rgba >> 24) & 0xff))); 546 | } 547 | 548 | static RgbaColor FromPackedIntBGRA(uint32_t bgra) { 549 | return From( 550 | (int)((bgra >> 16) & 0xff), 551 | (int)((bgra >> 8) & 0xff), 552 | (int)((bgra) & 0xff), 553 | (int)(255 - ((bgra >> 24) & 0xff))); 554 | } 555 | }; 556 | 557 | struct RgbaColorCompare { 558 | bool operator()(RgbaColor a, RgbaColor b) const { 559 | return a.ToARGB32() < b.ToARGB32(); 560 | } 561 | }; 562 | 563 | class BBox { 564 | public: 565 | Vector minp; 566 | Vector maxp; 567 | 568 | static BBox From(const Vector &p0, const Vector &p1); 569 | 570 | Vector GetOrigin() const; 571 | Vector GetExtents() const; 572 | 573 | void Include(const Vector &v, double r = 0.0); 574 | bool Overlaps(const BBox &b1) const; 575 | bool Contains(const Point2d &p, double r = 0.0) const; 576 | }; 577 | 578 | #endif 579 | -------------------------------------------------------------------------------- /src/expr.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // An expression in our symbolic algebra system, used to write, linearize, 3 | // and solve our constraint equations. 4 | // 5 | // Copyright 2008-2013 Jonathan Westhues. 6 | //----------------------------------------------------------------------------- 7 | #ifndef __EXPR_H 8 | #define __EXPR_H 9 | 10 | class Expr { 11 | public: 12 | 13 | enum class Op : uint32_t { 14 | // A parameter, by the hParam handle 15 | PARAM = 0, 16 | // A parameter, by a pointer straight in to the param table (faster, 17 | // if we know that the param table won't move around) 18 | PARAM_PTR = 1, 19 | 20 | // Operands 21 | CONSTANT = 20, 22 | VARIABLE = 21, 23 | 24 | // Binary ops 25 | PLUS = 100, 26 | MINUS = 101, 27 | TIMES = 102, 28 | DIV = 103, 29 | // Unary ops 30 | NEGATE = 104, 31 | SQRT = 105, 32 | SQUARE = 106, 33 | SIN = 107, 34 | COS = 108, 35 | ASIN = 109, 36 | ACOS = 110, 37 | }; 38 | 39 | Op op; 40 | Expr *a; 41 | union { 42 | double v; 43 | hParam parh; 44 | Param *parp; 45 | Expr *b; 46 | }; 47 | 48 | Expr() { } 49 | Expr(double val) : op(Op::CONSTANT) { v = val; } 50 | 51 | static inline Expr *AllocExpr() 52 | { return (Expr *)AllocTemporary(sizeof(Expr)); } 53 | 54 | static Expr *From(hParam p); 55 | static Expr *From(double v); 56 | 57 | Expr *AnyOp(Op op, Expr *b); 58 | inline Expr *Plus (Expr *b_) { return AnyOp(Op::PLUS, b_); } 59 | inline Expr *Minus(Expr *b_) { return AnyOp(Op::MINUS, b_); } 60 | inline Expr *Times(Expr *b_) { return AnyOp(Op::TIMES, b_); } 61 | inline Expr *Div (Expr *b_) { return AnyOp(Op::DIV, b_); } 62 | 63 | inline Expr *Negate() { return AnyOp(Op::NEGATE, NULL); } 64 | inline Expr *Sqrt () { return AnyOp(Op::SQRT, NULL); } 65 | inline Expr *Square() { return AnyOp(Op::SQUARE, NULL); } 66 | inline Expr *Sin () { return AnyOp(Op::SIN, NULL); } 67 | inline Expr *Cos () { return AnyOp(Op::COS, NULL); } 68 | inline Expr *ASin () { return AnyOp(Op::ASIN, NULL); } 69 | inline Expr *ACos () { return AnyOp(Op::ACOS, NULL); } 70 | 71 | Expr *PartialWrt(hParam p) const; 72 | double Eval() const; 73 | uint64_t ParamsUsed() const; 74 | bool DependsOn(hParam p) const; 75 | static bool Tol(double a, double b); 76 | Expr *FoldConstants(); 77 | void Substitute(hParam oldh, hParam newh); 78 | 79 | static const hParam NO_PARAMS, MULTIPLE_PARAMS; 80 | hParam ReferencedParams(ParamList *pl) const; 81 | 82 | void ParamsToPointers(); 83 | 84 | std::string Print() const; 85 | 86 | // number of child nodes: 0 (e.g. constant), 1 (sqrt), or 2 (+) 87 | int Children() const; 88 | // total number of nodes in the tree 89 | int Nodes() const; 90 | 91 | // Make a simple copy 92 | Expr *DeepCopy() const; 93 | // Make a copy, with the parameters (usually referenced by hParam) 94 | // resolved to pointers to the actual value. This speeds things up 95 | // considerably. 96 | Expr *DeepCopyWithParamsAsPointers(IdList *firstTry, 97 | IdList *thenTry) const; 98 | 99 | static Expr *Parse(const char *input, std::string *error); 100 | static Expr *From(const char *in, bool popUpError); 101 | }; 102 | 103 | class ExprVector { 104 | public: 105 | Expr *x, *y, *z; 106 | 107 | static ExprVector From(Expr *x, Expr *y, Expr *z); 108 | static ExprVector From(Vector vn); 109 | static ExprVector From(hParam x, hParam y, hParam z); 110 | static ExprVector From(double x, double y, double z); 111 | 112 | ExprVector Plus(ExprVector b) const; 113 | ExprVector Minus(ExprVector b) const; 114 | Expr *Dot(ExprVector b) const; 115 | ExprVector Cross(ExprVector b) const; 116 | ExprVector ScaledBy(Expr *s) const; 117 | ExprVector WithMagnitude(Expr *s) const; 118 | Expr *Magnitude() const; 119 | 120 | Vector Eval() const; 121 | }; 122 | 123 | class ExprQuaternion { 124 | public: 125 | Expr *w, *vx, *vy, *vz; 126 | 127 | static ExprQuaternion From(Expr *w, Expr *vx, Expr *vy, Expr *vz); 128 | static ExprQuaternion From(Quaternion qn); 129 | static ExprQuaternion From(hParam w, hParam vx, hParam vy, hParam vz); 130 | 131 | ExprVector RotationU() const; 132 | ExprVector RotationV() const; 133 | ExprVector RotationN() const; 134 | 135 | ExprVector Rotate(ExprVector p) const; 136 | ExprQuaternion Times(ExprQuaternion b) const; 137 | 138 | Expr *Magnitude() const; 139 | }; 140 | #endif 141 | -------------------------------------------------------------------------------- /src/lib.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // A library wrapper around SolveSpace, to permit someone to use its constraint 3 | // solver without coupling their program too much to SolveSpace's internals. 4 | // 5 | // Copyright 2008-2013 Jonathan Westhues. 6 | //----------------------------------------------------------------------------- 7 | #include "solvespace.h" 8 | #define EXPORT_DLL 9 | #include 10 | 11 | Sketch SolveSpace::SK = {}; 12 | static System SYS; 13 | 14 | static int IsInit = 0; 15 | 16 | void Group::GenerateEquations(IdList *) { 17 | // Nothing to do for now. 18 | } 19 | 20 | void SolveSpace::CnfFreezeInt(uint32_t, const std::string &) 21 | { 22 | abort(); 23 | } 24 | 25 | uint32_t SolveSpace::CnfThawInt(uint32_t, const std::string &) 26 | { 27 | abort(); 28 | return 0; 29 | } 30 | 31 | void SolveSpace::DoMessageBox(const char *, int, int, bool) 32 | { 33 | abort(); 34 | } 35 | 36 | extern "C" { 37 | 38 | void Slvs_QuaternionU(double qw, double qx, double qy, double qz, 39 | double *x, double *y, double *z) 40 | { 41 | Quaternion q = Quaternion::From(qw, qx, qy, qz); 42 | Vector v = q.RotationU(); 43 | *x = v.x; 44 | *y = v.y; 45 | *z = v.z; 46 | } 47 | 48 | void Slvs_QuaternionV(double qw, double qx, double qy, double qz, 49 | double *x, double *y, double *z) 50 | { 51 | Quaternion q = Quaternion::From(qw, qx, qy, qz); 52 | Vector v = q.RotationV(); 53 | *x = v.x; 54 | *y = v.y; 55 | *z = v.z; 56 | } 57 | 58 | void Slvs_QuaternionN(double qw, double qx, double qy, double qz, 59 | double *x, double *y, double *z) 60 | { 61 | Quaternion q = Quaternion::From(qw, qx, qy, qz); 62 | Vector v = q.RotationN(); 63 | *x = v.x; 64 | *y = v.y; 65 | *z = v.z; 66 | } 67 | 68 | void Slvs_MakeQuaternion(double ux, double uy, double uz, 69 | double vx, double vy, double vz, 70 | double *qw, double *qx, double *qy, double *qz) 71 | { 72 | Vector u = Vector::From(ux, uy, uz), 73 | v = Vector::From(vx, vy, vz); 74 | Quaternion q = Quaternion::From(u, v); 75 | *qw = q.w; 76 | *qx = q.vx; 77 | *qy = q.vy; 78 | *qz = q.vz; 79 | } 80 | 81 | void Slvs_Solve(Slvs_System *ssys, Slvs_hGroup shg) 82 | { 83 | if(!IsInit) { 84 | InitPlatform(0, NULL); 85 | IsInit = 1; 86 | } 87 | 88 | int i; 89 | for(i = 0; i < ssys->params; i++) { 90 | Slvs_Param *sp = &(ssys->param[i]); 91 | Param p = {}; 92 | 93 | p.h.v = sp->h; 94 | p.val = sp->val; 95 | SK.param.Add(&p); 96 | if(sp->group == shg) { 97 | SYS.param.Add(&p); 98 | } 99 | } 100 | 101 | for(i = 0; i < ssys->entities; i++) { 102 | Slvs_Entity *se = &(ssys->entity[i]); 103 | EntityBase e = {}; 104 | 105 | switch(se->type) { 106 | case SLVS_E_POINT_IN_3D: e.type = Entity::Type::POINT_IN_3D; break; 107 | case SLVS_E_POINT_IN_2D: e.type = Entity::Type::POINT_IN_2D; break; 108 | case SLVS_E_NORMAL_IN_3D: e.type = Entity::Type::NORMAL_IN_3D; break; 109 | case SLVS_E_NORMAL_IN_2D: e.type = Entity::Type::NORMAL_IN_2D; break; 110 | case SLVS_E_DISTANCE: e.type = Entity::Type::DISTANCE; break; 111 | case SLVS_E_WORKPLANE: e.type = Entity::Type::WORKPLANE; break; 112 | case SLVS_E_LINE_SEGMENT: e.type = Entity::Type::LINE_SEGMENT; break; 113 | case SLVS_E_CUBIC: e.type = Entity::Type::CUBIC; break; 114 | case SLVS_E_CIRCLE: e.type = Entity::Type::CIRCLE; break; 115 | case SLVS_E_ARC_OF_CIRCLE: e.type = Entity::Type::ARC_OF_CIRCLE; break; 116 | 117 | default: dbp("bad entity type %d", se->type); return; 118 | } 119 | e.h.v = se->h; 120 | e.group.v = se->group; 121 | e.workplane.v = se->wrkpl; 122 | e.point[0].v = se->point[0]; 123 | e.point[1].v = se->point[1]; 124 | e.point[2].v = se->point[2]; 125 | e.point[3].v = se->point[3]; 126 | e.normal.v = se->normal; 127 | e.distance.v = se->distance; 128 | e.param[0].v = se->param[0]; 129 | e.param[1].v = se->param[1]; 130 | e.param[2].v = se->param[2]; 131 | e.param[3].v = se->param[3]; 132 | 133 | SK.entity.Add(&e); 134 | } 135 | IdList params = {}; 136 | for(i = 0; i < ssys->constraints; i++) { 137 | Slvs_Constraint *sc = &(ssys->constraint[i]); 138 | ConstraintBase c = {}; 139 | 140 | Constraint::Type t; 141 | switch(sc->type) { 142 | case SLVS_C_POINTS_COINCIDENT: t = Constraint::Type::POINTS_COINCIDENT; break; 143 | case SLVS_C_PT_PT_DISTANCE: t = Constraint::Type::PT_PT_DISTANCE; break; 144 | case SLVS_C_PT_PLANE_DISTANCE: t = Constraint::Type::PT_PLANE_DISTANCE; break; 145 | case SLVS_C_PT_LINE_DISTANCE: t = Constraint::Type::PT_LINE_DISTANCE; break; 146 | case SLVS_C_PT_FACE_DISTANCE: t = Constraint::Type::PT_FACE_DISTANCE; break; 147 | case SLVS_C_PT_IN_PLANE: t = Constraint::Type::PT_IN_PLANE; break; 148 | case SLVS_C_PT_ON_LINE: t = Constraint::Type::PT_ON_LINE; break; 149 | case SLVS_C_PT_ON_FACE: t = Constraint::Type::PT_ON_FACE; break; 150 | case SLVS_C_EQUAL_LENGTH_LINES: t = Constraint::Type::EQUAL_LENGTH_LINES; break; 151 | case SLVS_C_LENGTH_RATIO: t = Constraint::Type::LENGTH_RATIO; break; 152 | case SLVS_C_EQ_LEN_PT_LINE_D: t = Constraint::Type::EQ_LEN_PT_LINE_D; break; 153 | case SLVS_C_EQ_PT_LN_DISTANCES: t = Constraint::Type::EQ_PT_LN_DISTANCES; break; 154 | case SLVS_C_EQUAL_ANGLE: t = Constraint::Type::EQUAL_ANGLE; break; 155 | case SLVS_C_EQUAL_LINE_ARC_LEN: t = Constraint::Type::EQUAL_LINE_ARC_LEN; break; 156 | case SLVS_C_LENGTH_DIFFERENCE: t = Constraint::Type::LENGTH_DIFFERENCE; break; 157 | case SLVS_C_SYMMETRIC: t = Constraint::Type::SYMMETRIC; break; 158 | case SLVS_C_SYMMETRIC_HORIZ: t = Constraint::Type::SYMMETRIC_HORIZ; break; 159 | case SLVS_C_SYMMETRIC_VERT: t = Constraint::Type::SYMMETRIC_VERT; break; 160 | case SLVS_C_SYMMETRIC_LINE: t = Constraint::Type::SYMMETRIC_LINE; break; 161 | case SLVS_C_AT_MIDPOINT: t = Constraint::Type::AT_MIDPOINT; break; 162 | case SLVS_C_HORIZONTAL: t = Constraint::Type::HORIZONTAL; break; 163 | case SLVS_C_VERTICAL: t = Constraint::Type::VERTICAL; break; 164 | case SLVS_C_DIAMETER: t = Constraint::Type::DIAMETER; break; 165 | case SLVS_C_PT_ON_CIRCLE: t = Constraint::Type::PT_ON_CIRCLE; break; 166 | case SLVS_C_SAME_ORIENTATION: t = Constraint::Type::SAME_ORIENTATION; break; 167 | case SLVS_C_ANGLE: t = Constraint::Type::ANGLE; break; 168 | case SLVS_C_PARALLEL: t = Constraint::Type::PARALLEL; break; 169 | case SLVS_C_PERPENDICULAR: t = Constraint::Type::PERPENDICULAR; break; 170 | case SLVS_C_ARC_LINE_TANGENT: t = Constraint::Type::ARC_LINE_TANGENT; break; 171 | case SLVS_C_CUBIC_LINE_TANGENT: t = Constraint::Type::CUBIC_LINE_TANGENT; break; 172 | case SLVS_C_EQUAL_RADIUS: t = Constraint::Type::EQUAL_RADIUS; break; 173 | case SLVS_C_PROJ_PT_DISTANCE: t = Constraint::Type::PROJ_PT_DISTANCE; break; 174 | case SLVS_C_WHERE_DRAGGED: t = Constraint::Type::WHERE_DRAGGED; break; 175 | case SLVS_C_CURVE_CURVE_TANGENT:t = Constraint::Type::CURVE_CURVE_TANGENT; break; 176 | 177 | default: dbp("bad constraint type %d", sc->type); return; 178 | } 179 | 180 | c.type = t; 181 | 182 | c.h.v = sc->h; 183 | c.group.v = sc->group; 184 | c.workplane.v = sc->wrkpl; 185 | c.valA = sc->valA; 186 | c.ptA.v = sc->ptA; 187 | c.ptB.v = sc->ptB; 188 | c.entityA.v = sc->entityA; 189 | c.entityB.v = sc->entityB; 190 | c.entityC.v = sc->entityC; 191 | c.entityD.v = sc->entityD; 192 | c.other = (sc->other) ? true : false; 193 | c.other2 = (sc->other2) ? true : false; 194 | 195 | c.Generate(¶ms); 196 | if(params.n > 0) { 197 | for(Param &p : params) { 198 | p.h = SK.param.AddAndAssignId(&p); 199 | c.valP = p.h; 200 | SYS.param.Add(&p); 201 | } 202 | params.Clear(); 203 | c.ModifyToSatisfy(); 204 | } 205 | 206 | SK.constraint.Add(&c); 207 | } 208 | 209 | for(i = 0; i < (int)arraylen(ssys->dragged); i++) { 210 | if(ssys->dragged[i]) { 211 | hParam hp = { ssys->dragged[i] }; 212 | SYS.dragged.Add(&hp); 213 | } 214 | } 215 | 216 | Group g = {}; 217 | g.h.v = shg; 218 | 219 | List bad = {}; 220 | 221 | // Now we're finally ready to solve! 222 | bool andFindBad = ssys->calculateFaileds ? true : false; 223 | SolveResult how = SYS.Solve(&g, &(ssys->dof), &bad, andFindBad, /*andFindFree=*/false); 224 | 225 | switch(how) { 226 | case SolveResult::OKAY: 227 | ssys->result = SLVS_RESULT_OKAY; 228 | break; 229 | 230 | case SolveResult::DIDNT_CONVERGE: 231 | ssys->result = SLVS_RESULT_DIDNT_CONVERGE; 232 | break; 233 | 234 | case SolveResult::REDUNDANT_DIDNT_CONVERGE: 235 | case SolveResult::REDUNDANT_OKAY: 236 | ssys->result = SLVS_RESULT_INCONSISTENT; 237 | break; 238 | 239 | case SolveResult::TOO_MANY_UNKNOWNS: 240 | ssys->result = SLVS_RESULT_TOO_MANY_UNKNOWNS; 241 | break; 242 | } 243 | 244 | // Write the new parameter values back to our caller. 245 | for(i = 0; i < ssys->params; i++) { 246 | Slvs_Param *sp = &(ssys->param[i]); 247 | hParam hp = { sp->h }; 248 | sp->val = SK.GetParam(hp)->val; 249 | } 250 | 251 | if(ssys->failed) { 252 | // Copy over any the list of problematic constraints. 253 | for(i = 0; i < ssys->faileds && i < bad.n; i++) { 254 | ssys->failed[i] = bad.elem[i].v; 255 | } 256 | ssys->faileds = bad.n; 257 | } 258 | 259 | bad.Clear(); 260 | SYS.param.Clear(); 261 | SYS.entity.Clear(); 262 | SYS.eq.Clear(); 263 | SYS.dragged.Clear(); 264 | 265 | SK.param.Clear(); 266 | SK.entity.Clear(); 267 | SK.constraint.Clear(); 268 | 269 | FreeAllTemporary(); 270 | } 271 | 272 | } /* extern "C" */ 273 | -------------------------------------------------------------------------------- /src/platform/platform.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Platform-dependent functionality. 3 | // 4 | // Copyright 2017 whitequark 5 | //----------------------------------------------------------------------------- 6 | #if defined(__APPLE__) 7 | // Include Apple headers before solvespace.h to avoid identifier clashes. 8 | # include 9 | # include 10 | # include 11 | #endif 12 | #include "solvespace.h" 13 | #include "config.h" 14 | #if defined(WIN32) 15 | // Conversely, include Microsoft headers after solvespace.h to avoid clashes. 16 | # include 17 | #else 18 | # include 19 | # include 20 | #endif 21 | 22 | namespace SolveSpace { 23 | namespace Platform { 24 | 25 | //----------------------------------------------------------------------------- 26 | // UTF-8 ⟷ UTF-16 conversion, on Windows. 27 | //----------------------------------------------------------------------------- 28 | 29 | #if defined(WIN32) 30 | 31 | std::string Narrow(const wchar_t *in) 32 | { 33 | std::string out; 34 | DWORD len = WideCharToMultiByte(CP_UTF8, 0, in, -1, NULL, 0, NULL, NULL); 35 | out.resize(len - 1); 36 | ssassert(WideCharToMultiByte(CP_UTF8, 0, in, -1, &out[0], len, NULL, NULL), 37 | "Invalid UTF-16"); 38 | return out; 39 | } 40 | 41 | std::string Narrow(const std::wstring &in) 42 | { 43 | if(in == L"") return ""; 44 | 45 | std::string out; 46 | out.resize(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(), 47 | NULL, 0, NULL, NULL)); 48 | ssassert(WideCharToMultiByte(CP_UTF8, 0, &in[0], (int)in.length(), 49 | &out[0], (int)out.length(), NULL, NULL), 50 | "Invalid UTF-16"); 51 | return out; 52 | } 53 | 54 | std::wstring Widen(const char *in) 55 | { 56 | std::wstring out; 57 | DWORD len = MultiByteToWideChar(CP_UTF8, 0, in, -1, NULL, 0); 58 | out.resize(len - 1); 59 | ssassert(MultiByteToWideChar(CP_UTF8, 0, in, -1, &out[0], len), 60 | "Invalid UTF-8"); 61 | return out; 62 | } 63 | 64 | std::wstring Widen(const std::string &in) 65 | { 66 | if(in == "") return L""; 67 | 68 | std::wstring out; 69 | out.resize(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), NULL, 0)); 70 | ssassert(MultiByteToWideChar(CP_UTF8, 0, &in[0], (int)in.length(), 71 | &out[0], (int)out.length()), 72 | "Invalid UTF-8"); 73 | return out; 74 | } 75 | 76 | #endif 77 | 78 | //----------------------------------------------------------------------------- 79 | // Path utility functions. 80 | //----------------------------------------------------------------------------- 81 | 82 | static std::vector Split(const std::string &joined, char separator) { 83 | std::vector parts; 84 | 85 | size_t oldpos = 0, pos = 0; 86 | while(true) { 87 | oldpos = pos; 88 | pos = joined.find(separator, pos); 89 | if(pos == std::string::npos) break; 90 | parts.push_back(joined.substr(oldpos, pos - oldpos)); 91 | pos += 1; 92 | } 93 | 94 | if(oldpos != joined.length() - 1) { 95 | parts.push_back(joined.substr(oldpos)); 96 | } 97 | 98 | return parts; 99 | } 100 | 101 | static std::string Concat(const std::vector &parts, char separator) { 102 | std::string joined; 103 | 104 | bool first = true; 105 | for(auto &part : parts) { 106 | if(!first) joined += separator; 107 | joined += part; 108 | first = false; 109 | } 110 | 111 | return joined; 112 | } 113 | 114 | //----------------------------------------------------------------------------- 115 | // Path manipulation. 116 | //----------------------------------------------------------------------------- 117 | 118 | #if defined(WIN32) 119 | const char SEPARATOR = '\\'; 120 | #else 121 | const char SEPARATOR = '/'; 122 | #endif 123 | 124 | Path Path::From(std::string raw) { 125 | Path path = { raw }; 126 | return path; 127 | } 128 | 129 | Path Path::CurrentDirectory() { 130 | #if defined(WIN32) 131 | // On Windows, OpenFile needs an absolute UNC path proper, so get that. 132 | std::wstring rawW; 133 | rawW.resize(GetCurrentDirectoryW(0, NULL)); 134 | DWORD length = GetCurrentDirectoryW((int)rawW.length(), &rawW[0]); 135 | ssassert(length > 0 && length == rawW.length() - 1, "Cannot get current directory"); 136 | rawW.resize(length); 137 | return From(Narrow(rawW)); 138 | #else 139 | char *raw = getcwd(NULL, 0); 140 | ssassert(raw != NULL, "Cannot get current directory"); 141 | Path path = From(raw); 142 | free(raw); 143 | return path; 144 | #endif 145 | } 146 | 147 | std::string Path::FileName() const { 148 | std::string fileName = raw; 149 | size_t slash = fileName.rfind(SEPARATOR); 150 | if(slash != std::string::npos) { 151 | fileName = fileName.substr(slash + 1); 152 | } 153 | return fileName; 154 | } 155 | 156 | std::string Path::FileStem() const { 157 | std::string baseName = FileName(); 158 | size_t dot = baseName.rfind('.'); 159 | if(dot != std::string::npos) { 160 | baseName = baseName.substr(0, dot); 161 | } 162 | return baseName; 163 | } 164 | 165 | std::string Path::Extension() const { 166 | size_t dot = raw.rfind('.'); 167 | if(dot != std::string::npos) { 168 | return raw.substr(dot + 1); 169 | } 170 | return ""; 171 | } 172 | 173 | bool Path::HasExtension(std::string theirExt) const { 174 | std::string ourExt = Extension(); 175 | std::transform(ourExt.begin(), ourExt.end(), ourExt.begin(), ::tolower); 176 | std::transform(theirExt.begin(), theirExt.end(), theirExt.begin(), ::tolower); 177 | return ourExt == theirExt; 178 | } 179 | 180 | Path Path::WithExtension(std::string ext) const { 181 | Path withExt = *this; 182 | size_t dot = withExt.raw.rfind('.'); 183 | if(dot != std::string::npos) { 184 | withExt.raw.erase(dot); 185 | } 186 | withExt.raw += "."; 187 | withExt.raw += ext; 188 | return withExt; 189 | } 190 | 191 | static void FindPrefix(const std::string &raw, size_t *pos) { 192 | *pos = std::string::npos; 193 | #if defined(WIN32) 194 | if(raw.size() >= 7 && raw[2] == '?' && raw[3] == '\\' && 195 | isalpha(raw[4]) && raw[5] == ':' && raw[6] == '\\') { 196 | *pos = 7; 197 | } else if(raw.size() >= 3 && isalpha(raw[0]) && raw[1] == ':' && raw[2] == '\\') { 198 | *pos = 3; 199 | } else if(raw.size() >= 2 && raw[0] == '\\' && raw[1] == '\\') { 200 | size_t slashAt = raw.find('\\', 2); 201 | if(slashAt != std::string::npos) { 202 | *pos = raw.find('\\', slashAt + 1); 203 | } 204 | } 205 | #else 206 | if(raw.size() >= 1 && raw[0] == '/') { 207 | *pos = 1; 208 | } 209 | #endif 210 | } 211 | 212 | bool Path::IsAbsolute() const { 213 | size_t pos; 214 | FindPrefix(raw, &pos); 215 | return pos != std::string::npos; 216 | } 217 | 218 | // Removes one component from the end of the path. 219 | // Returns an empty path if the path consists only of a root. 220 | Path Path::Parent() const { 221 | Path parent = { raw }; 222 | if(!parent.raw.empty() && parent.raw.back() == SEPARATOR) { 223 | parent.raw.pop_back(); 224 | } 225 | size_t slash = parent.raw.rfind(SEPARATOR); 226 | if(slash != std::string::npos) { 227 | parent.raw = parent.raw.substr(0, slash + 1); 228 | } else { 229 | parent.raw.clear(); 230 | } 231 | if(IsAbsolute() && !parent.IsAbsolute()) { 232 | return From(""); 233 | } 234 | return parent; 235 | } 236 | 237 | // Concatenates a component to this path. 238 | // Returns an empty path if this path or the component is empty. 239 | Path Path::Join(const std::string &component) const { 240 | ssassert(component.find(SEPARATOR) == std::string::npos, 241 | "Use the Path::Join(const Path &) overload to append an entire path"); 242 | return Join(Path::From(component)); 243 | } 244 | 245 | // Concatenates a relative path to this path. 246 | // Returns an empty path if either path is empty, or the other path is absolute. 247 | Path Path::Join(const Path &other) const { 248 | if(IsEmpty() || other.IsEmpty() || other.IsAbsolute()) { 249 | return From(""); 250 | } 251 | 252 | Path joined = { raw }; 253 | if(joined.raw.back() != SEPARATOR) { 254 | joined.raw += SEPARATOR; 255 | } 256 | joined.raw += other.raw; 257 | return joined; 258 | } 259 | 260 | // Expands the "." and ".." components in this path. 261 | // On Windows, additionally prepends the UNC prefix to absolute paths without one. 262 | // Returns an empty path if a ".." component would escape from the root. 263 | Path Path::Expand(bool fromCurrentDirectory) const { 264 | Path source; 265 | Path expanded; 266 | 267 | if(fromCurrentDirectory && !IsAbsolute()) { 268 | source = CurrentDirectory().Join(*this); 269 | } else { 270 | source = *this; 271 | } 272 | 273 | size_t splitAt; 274 | FindPrefix(source.raw, &splitAt); 275 | if(splitAt != std::string::npos) { 276 | expanded.raw = source.raw.substr(0, splitAt); 277 | } else { 278 | splitAt = 0; 279 | } 280 | 281 | std::vector expandedComponents; 282 | for(std::string component : Split(source.raw.substr(splitAt), SEPARATOR)) { 283 | if(component == ".") { 284 | // skip 285 | } else if(component == "..") { 286 | if(!expandedComponents.empty()) { 287 | expandedComponents.pop_back(); 288 | } else { 289 | return From(""); 290 | } 291 | } else if(!component.empty()) { 292 | expandedComponents.push_back(component); 293 | } 294 | } 295 | 296 | if(expanded.IsEmpty()) { 297 | if(expandedComponents.empty()) { 298 | expandedComponents.push_back("."); 299 | } 300 | expanded = From(Concat(expandedComponents, SEPARATOR)); 301 | } else if(!expandedComponents.empty()) { 302 | expanded = expanded.Join(From(Concat(expandedComponents, SEPARATOR))); 303 | } 304 | 305 | #if defined(WIN32) 306 | if(expanded.IsAbsolute() && expanded.raw.substr(0, 2) != "\\\\") { 307 | expanded.raw = "\\\\?\\" + expanded.raw; 308 | } 309 | #endif 310 | 311 | return expanded; 312 | } 313 | 314 | static std::string FilesystemNormalize(const std::string &str) { 315 | #if defined(WIN32) 316 | std::wstring strW = Widen(str); 317 | std::transform(strW.begin(), strW.end(), strW.begin(), towlower); 318 | return Narrow(strW); 319 | #elif defined(__APPLE__) 320 | CFMutableStringRef cfStr = 321 | CFStringCreateMutableCopy(NULL, 0, 322 | CFStringCreateWithBytesNoCopy(NULL, (const UInt8*)str.data(), str.size(), 323 | kCFStringEncodingUTF8, /*isExternalRepresentation=*/false, kCFAllocatorNull)); 324 | CFStringLowercase(cfStr, NULL); 325 | std::string normalizedStr; 326 | normalizedStr.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfStr)); 327 | CFStringGetFileSystemRepresentation(cfStr, &normalizedStr[0], normalizedStr.size()); 328 | normalizedStr.erase(normalizedStr.find('\0')); 329 | return normalizedStr; 330 | #else 331 | return str; 332 | #endif 333 | } 334 | 335 | bool Path::Equals(const Path &other) const { 336 | return FilesystemNormalize(raw) == FilesystemNormalize(other.raw); 337 | } 338 | 339 | // Returns a relative path from a given base path. 340 | // Returns an empty path if any of the paths is not absolute, or 341 | // if they belong to different roots, or 342 | // if they cannot be expanded. 343 | Path Path::RelativeTo(const Path &base) const { 344 | Path expanded = Expand(); 345 | Path baseExpanded = base.Expand(); 346 | if(!(expanded.IsAbsolute() && baseExpanded.IsAbsolute())){ 347 | return From(""); 348 | } 349 | 350 | size_t splitAt; 351 | FindPrefix(expanded.raw, &splitAt); 352 | size_t baseSplitAt; 353 | FindPrefix(baseExpanded.raw, &baseSplitAt); 354 | if(FilesystemNormalize(expanded.raw.substr(0, splitAt)) != 355 | FilesystemNormalize(baseExpanded.raw.substr(0, splitAt))) { 356 | return From(""); 357 | } 358 | 359 | std::vector components = 360 | Split(expanded.raw.substr(splitAt), SEPARATOR); 361 | std::vector baseComponents = 362 | Split(baseExpanded.raw.substr(baseSplitAt), SEPARATOR); 363 | size_t common; 364 | for(common = 0; common < baseComponents.size() && 365 | common < components.size(); common++) { 366 | if(FilesystemNormalize(baseComponents[common]) != 367 | FilesystemNormalize(components[common])) { 368 | break; 369 | } 370 | } 371 | 372 | std::vector resultComponents; 373 | for(size_t i = common; i < baseComponents.size(); i++) { 374 | resultComponents.push_back(".."); 375 | } 376 | resultComponents.insert(resultComponents.end(), 377 | components.begin() + common, components.end()); 378 | if(resultComponents.empty()) { 379 | resultComponents.push_back("."); 380 | } 381 | return From(Concat(resultComponents, SEPARATOR)); 382 | } 383 | 384 | Path Path::FromPortable(const std::string &repr) { 385 | return From(Concat(Split(repr, '/'), SEPARATOR)); 386 | } 387 | 388 | std::string Path::ToPortable() const { 389 | ssassert(!IsAbsolute(), "absolute paths cannot be made portable"); 390 | 391 | return Concat(Split(raw, SEPARATOR), '/'); 392 | } 393 | 394 | //----------------------------------------------------------------------------- 395 | // File manipulation. 396 | //----------------------------------------------------------------------------- 397 | 398 | FILE *OpenFile(const Platform::Path &filename, const char *mode) { 399 | ssassert(filename.raw.length() == strlen(filename.raw.c_str()), 400 | "Unexpected null byte in middle of a path"); 401 | #if defined(WIN32) 402 | return _wfopen(Widen(filename.Expand().raw).c_str(), Widen(mode).c_str()); 403 | #else 404 | return fopen(filename.raw.c_str(), mode); 405 | #endif 406 | } 407 | 408 | void RemoveFile(const Platform::Path &filename) { 409 | ssassert(filename.raw.length() == strlen(filename.raw.c_str()), 410 | "Unexpected null byte in middle of a path"); 411 | #if defined(WIN32) 412 | _wremove(Widen(filename.Expand().raw).c_str()); 413 | #else 414 | remove(filename.raw.c_str()); 415 | #endif 416 | } 417 | 418 | bool ReadFile(const Platform::Path &filename, std::string *data) { 419 | FILE *f = OpenFile(filename, "rb"); 420 | if(f == NULL) return false; 421 | 422 | fseek(f, 0, SEEK_END); 423 | data->resize(ftell(f)); 424 | fseek(f, 0, SEEK_SET); 425 | fread(&(*data)[0], 1, data->size(), f); 426 | fclose(f); 427 | 428 | return true; 429 | } 430 | 431 | bool WriteFile(const Platform::Path &filename, const std::string &data) { 432 | FILE *f = OpenFile(filename, "wb"); 433 | if(f == NULL) return false; 434 | 435 | fwrite(&data[0], 1, data.size(), f); 436 | fclose(f); 437 | 438 | return true; 439 | } 440 | 441 | //----------------------------------------------------------------------------- 442 | // Loading resources, on Windows. 443 | //----------------------------------------------------------------------------- 444 | 445 | #if defined(WIN32) && !defined(LIBRARY) 446 | 447 | const void *LoadResource(const std::string &name, size_t *size) { 448 | HRSRC hres = FindResourceW(NULL, Widen(name).c_str(), RT_RCDATA); 449 | ssassert(hres != NULL, "Cannot find resource"); 450 | HGLOBAL res = ::LoadResource(NULL, hres); 451 | ssassert(res != NULL, "Cannot load resource"); 452 | 453 | *size = SizeofResource(NULL, hres); 454 | return LockResource(res); 455 | } 456 | 457 | #endif 458 | 459 | //----------------------------------------------------------------------------- 460 | // Loading resources, on *nix. 461 | //----------------------------------------------------------------------------- 462 | 463 | #if defined(__APPLE__) 464 | 465 | static Platform::Path PathFromCFURL(CFURLRef cfUrl) { 466 | Path path; 467 | CFStringRef cfPath = CFURLCopyFileSystemPath(cfUrl, kCFURLPOSIXPathStyle); 468 | path.raw.resize(CFStringGetMaximumSizeOfFileSystemRepresentation(cfPath)); 469 | CFStringGetFileSystemRepresentation(cfPath, &path.raw[0], path.raw.size()); 470 | path.raw.erase(path.raw.find('\0')); 471 | CFRelease(cfPath); 472 | return path; 473 | } 474 | 475 | static Platform::Path ResourcePath(const std::string &name) { 476 | Path path; 477 | 478 | // First, try to get the URL from the bundle. 479 | CFStringRef cfName = CFStringCreateWithCString(kCFAllocatorDefault, name.c_str(), 480 | kCFStringEncodingUTF8); 481 | CFURLRef cfUrl = CFBundleCopyResourceURL(CFBundleGetMainBundle(), cfName, NULL, NULL); 482 | if(cfUrl != NULL) { 483 | path = PathFromCFURL(cfUrl); 484 | CFRelease(cfUrl); 485 | } 486 | CFRelease(cfName); 487 | 488 | if(!path.IsEmpty()) return path; 489 | 490 | // If that failed, it means we aren't running from the bundle. 491 | // Reference off the executable path, then. 492 | cfUrl = CFBundleCopyExecutableURL(CFBundleGetMainBundle()); 493 | if(cfUrl != NULL) { 494 | path = PathFromCFURL(cfUrl).Parent().Parent().Join("res"); 495 | path = path.Join(Path::FromPortable(name)); 496 | CFRelease(cfUrl); 497 | } 498 | 499 | return path; 500 | } 501 | 502 | #elif !defined(WIN32) 503 | 504 | # if defined(__linux__) 505 | static const char *selfSymlink = "/proc/self/exe"; 506 | # elif defined(__NetBSD__) 507 | static const char *selfSymlink = "/proc/curproc/exe"; 508 | # elif defined(__OpenBSD__) || defined(__FreeBSD__) 509 | static const char *selfSymlink = "/proc/curproc/file"; 510 | # else 511 | static const char *selfSymlink = ""; 512 | # endif 513 | 514 | static Platform::Path FindLocalResourceDir() { 515 | // Find out the path to the running binary. 516 | Platform::Path selfPath; 517 | char *expandedSelfPath = realpath(selfSymlink, NULL); 518 | if(expandedSelfPath != NULL) { 519 | selfPath = Path::From(expandedSelfPath); 520 | } 521 | free(expandedSelfPath); 522 | 523 | Platform::Path resourceDir; 524 | if(selfPath.IsEmpty()) { 525 | // We don't know how to find the local resource directory on this platform, 526 | // so use the global one (by returning an empty string). 527 | return Path::From(UNIX_DATADIR); 528 | } else { 529 | resourceDir = selfPath.Parent().Parent().Join("res"); 530 | } 531 | 532 | struct stat st; 533 | if(stat(resourceDir.raw.c_str(), &st) != -1) { 534 | // An executable-adjacent resource directory exists, good. 535 | return resourceDir; 536 | } 537 | 538 | // No executable-adjacent resource directory; use the one from compile-time prefix. 539 | return Path::From(UNIX_DATADIR); 540 | } 541 | 542 | static Platform::Path ResourcePath(const std::string &name) { 543 | static Platform::Path resourceDir; 544 | if(resourceDir.IsEmpty()) { 545 | resourceDir = FindLocalResourceDir(); 546 | } 547 | 548 | return resourceDir.Join(Path::FromPortable(name)); 549 | } 550 | 551 | #endif 552 | 553 | #if !defined(WIN32) 554 | 555 | const void *LoadResource(const std::string &name, size_t *size) { 556 | static std::map cache; 557 | 558 | auto it = cache.find(name); 559 | if(it == cache.end()) { 560 | ssassert(ReadFile(ResourcePath(name), &cache[name]), "Cannot read resource"); 561 | it = cache.find(name); 562 | } 563 | 564 | const std::string &content = (*it).second; 565 | *size = content.size(); 566 | return (const void*)content.data(); 567 | } 568 | 569 | #endif 570 | 571 | } 572 | } 573 | -------------------------------------------------------------------------------- /src/platform/platform.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Platform-dependent functionality. 3 | // 4 | // Copyright 2017 whitequark 5 | //----------------------------------------------------------------------------- 6 | 7 | #ifndef SOLVESPACE_PLATFORM_H 8 | #define SOLVESPACE_PLATFORM_H 9 | 10 | namespace Platform { 11 | 12 | // UTF-8 ⟷ UTF-16 conversion, for Windows. 13 | #if defined(WIN32) 14 | std::string Narrow(const wchar_t *s); 15 | std::wstring Widen(const char *s); 16 | std::string Narrow(const std::wstring &s); 17 | std::wstring Widen(const std::string &s); 18 | #endif 19 | 20 | // A filesystem path, respecting the conventions of the current platform. 21 | // Transformation functions return an empty path on error. 22 | class Path { 23 | public: 24 | std::string raw; 25 | 26 | static Path From(std::string raw); 27 | static Path CurrentDirectory(); 28 | 29 | void Clear() { raw.clear(); } 30 | 31 | bool Equals(const Path &other) const; 32 | bool IsEmpty() const { return raw.empty(); } 33 | bool IsAbsolute() const; 34 | bool HasExtension(std::string ext) const; 35 | 36 | std::string FileName() const; 37 | std::string FileStem() const; 38 | std::string Extension() const; 39 | 40 | Path WithExtension(std::string ext) const; 41 | Path Parent() const; 42 | Path Join(const std::string &component) const; 43 | Path Join(const Path &other) const; 44 | Path Expand(bool fromCurrentDirectory = false) const; 45 | Path RelativeTo(const Path &base) const; 46 | 47 | // Converting to and from a platform-independent representation 48 | // (conventionally, the Unix one). 49 | static Path FromPortable(const std::string &repr); 50 | std::string ToPortable() const; 51 | }; 52 | 53 | struct PathLess { 54 | bool operator()(const Path &a, const Path &b) const { return a.raw < b.raw; } 55 | }; 56 | 57 | // File manipulation functions. 58 | FILE *OpenFile(const Platform::Path &filename, const char *mode); 59 | bool ReadFile(const Platform::Path &filename, std::string *data); 60 | bool WriteFile(const Platform::Path &filename, const std::string &data); 61 | void RemoveFile(const Platform::Path &filename); 62 | 63 | // Resource loading function. 64 | const void *LoadResource(const std::string &name, size_t *size); 65 | 66 | } 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /src/platform/unixutil.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Utility functions used by the Unix port. Notably, our memory allocation; 3 | // we use two separate allocators, one for long-lived stuff and one for 4 | // stuff that gets freed after every regeneration of the model, to save us 5 | // the trouble of freeing the latter explicitly. 6 | // 7 | // Copyright 2008-2013 Jonathan Westhues. 8 | // Copyright 2013 Daniel Richard G. 9 | //----------------------------------------------------------------------------- 10 | #include 11 | #include "solvespace.h" 12 | 13 | namespace SolveSpace { 14 | 15 | void dbp(const char *str, ...) 16 | { 17 | va_list f; 18 | static char buf[1024*50]; 19 | va_start(f, str); 20 | vsnprintf(buf, sizeof(buf), str, f); 21 | va_end(f); 22 | 23 | fputs(buf, stderr); 24 | fputc('\n', stderr); 25 | } 26 | 27 | void assert_failure(const char *file, unsigned line, const char *function, 28 | const char *condition, const char *message) { 29 | fprintf(stderr, "File %s, line %u, function %s:\n", file, line, function); 30 | fprintf(stderr, "Assertion '%s' failed: ((%s) == false).\n", message, condition); 31 | 32 | #ifndef LIBRARY 33 | static void *ptrs[1024] = {}; 34 | size_t nptrs = backtrace(ptrs, sizeof(ptrs) / sizeof(ptrs[0])); 35 | char **syms = backtrace_symbols(ptrs, nptrs); 36 | 37 | fprintf(stderr, "Backtrace:\n"); 38 | if(syms != NULL) { 39 | for(size_t i = 0; i < nptrs; i++) { 40 | fprintf(stderr, "%2zu: %s\n", i, syms[i]); 41 | } 42 | } else { 43 | for(size_t i = 0; i < nptrs; i++) { 44 | fprintf(stderr, "%2zu: %p\n", i, ptrs[i]); 45 | } 46 | } 47 | #endif 48 | 49 | abort(); 50 | } 51 | 52 | //----------------------------------------------------------------------------- 53 | // A separate heap, on which we allocate expressions. Maybe a bit faster, 54 | // since fragmentation is less of a concern, and it also makes it possible 55 | // to be sloppy with our memory management, and just free everything at once 56 | // at the end. 57 | //----------------------------------------------------------------------------- 58 | 59 | typedef struct _AllocTempHeader AllocTempHeader; 60 | 61 | typedef struct _AllocTempHeader { 62 | AllocTempHeader *prev; 63 | AllocTempHeader *next; 64 | } AllocTempHeader; 65 | 66 | static AllocTempHeader *Head = NULL; 67 | 68 | void *AllocTemporary(size_t n) 69 | { 70 | AllocTempHeader *h = 71 | (AllocTempHeader *)malloc(n + sizeof(AllocTempHeader)); 72 | h->prev = NULL; 73 | h->next = Head; 74 | if(Head) Head->prev = h; 75 | Head = h; 76 | memset(&h[1], 0, n); 77 | return (void *)&h[1]; 78 | } 79 | 80 | void FreeTemporary(void *p) 81 | { 82 | AllocTempHeader *h = (AllocTempHeader *)p - 1; 83 | if(h->prev) { 84 | h->prev->next = h->next; 85 | } else { 86 | Head = h->next; 87 | } 88 | if(h->next) h->next->prev = h->prev; 89 | free(h); 90 | } 91 | 92 | void FreeAllTemporary(void) 93 | { 94 | AllocTempHeader *h = Head; 95 | while(h) { 96 | AllocTempHeader *f = h; 97 | h = h->next; 98 | free(f); 99 | } 100 | Head = NULL; 101 | } 102 | 103 | void *MemAlloc(size_t n) { 104 | void *p = malloc(n); 105 | ssassert(p != NULL, "Cannot allocate memory"); 106 | return p; 107 | } 108 | 109 | void MemFree(void *p) { 110 | free(p); 111 | } 112 | 113 | std::vector InitPlatform(int argc, char **argv) { 114 | std::vector args; 115 | for(int i = 0; i < argc; i++) { 116 | args.push_back(argv[i]); 117 | } 118 | return args; 119 | } 120 | 121 | }; 122 | -------------------------------------------------------------------------------- /src/platform/w32util.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Utility functions that depend on Win32. Notably, our memory allocation; 3 | // we use two separate allocators, one for long-lived stuff and one for 4 | // stuff that gets freed after every regeneration of the model, to save us 5 | // the trouble of freeing the latter explicitly. 6 | // 7 | // Copyright 2008-2013 Jonathan Westhues. 8 | //----------------------------------------------------------------------------- 9 | #include "solvespace.h" 10 | 11 | // Include after solvespace.h to avoid identifier clashes. 12 | #include 13 | #include 14 | 15 | namespace SolveSpace { 16 | static HANDLE PermHeap, TempHeap; 17 | 18 | void dbp(const char *str, ...) 19 | { 20 | va_list f; 21 | static char buf[1024*50]; 22 | va_start(f, str); 23 | _vsnprintf(buf, sizeof(buf), str, f); 24 | va_end(f); 25 | 26 | // The native version of OutputDebugString, unlike most others, 27 | // is OutputDebugStringA. 28 | OutputDebugStringA(buf); 29 | OutputDebugStringA("\n"); 30 | 31 | #ifndef NDEBUG 32 | // Duplicate to stderr in debug builds, but not in release; this is slow. 33 | fputs(buf, stderr); 34 | fputc('\n', stderr); 35 | #endif 36 | } 37 | 38 | void assert_failure(const char *file, unsigned line, const char *function, 39 | const char *condition, const char *message) { 40 | dbp("File %s, line %u, function %s:", file, line, function); 41 | dbp("Assertion '%s' failed: ((%s) == false).", message, condition); 42 | #ifdef NDEBUG 43 | _exit(1); 44 | #else 45 | abort(); 46 | #endif 47 | } 48 | 49 | //----------------------------------------------------------------------------- 50 | // A separate heap, on which we allocate expressions. Maybe a bit faster, 51 | // since no fragmentation issues whatsoever, and it also makes it possible 52 | // to be sloppy with our memory management, and just free everything at once 53 | // at the end. 54 | //----------------------------------------------------------------------------- 55 | void *AllocTemporary(size_t n) 56 | { 57 | void *v = HeapAlloc(TempHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, n); 58 | ssassert(v != NULL, "Cannot allocate memory"); 59 | return v; 60 | } 61 | void FreeTemporary(void *p) { 62 | HeapFree(TempHeap, HEAP_NO_SERIALIZE, p); 63 | } 64 | void FreeAllTemporary() 65 | { 66 | if(TempHeap) HeapDestroy(TempHeap); 67 | TempHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024*1024*20, 0); 68 | // This is a good place to validate, because it gets called fairly 69 | // often. 70 | vl(); 71 | } 72 | 73 | void *MemAlloc(size_t n) { 74 | void *p = HeapAlloc(PermHeap, HEAP_NO_SERIALIZE | HEAP_ZERO_MEMORY, n); 75 | ssassert(p != NULL, "Cannot allocate memory"); 76 | return p; 77 | } 78 | void MemFree(void *p) { 79 | HeapFree(PermHeap, HEAP_NO_SERIALIZE, p); 80 | } 81 | 82 | void vl() { 83 | ssassert(HeapValidate(TempHeap, HEAP_NO_SERIALIZE, NULL), "Corrupted heap"); 84 | ssassert(HeapValidate(PermHeap, HEAP_NO_SERIALIZE, NULL), "Corrupted heap"); 85 | } 86 | 87 | std::vector InitPlatform(int argc, char **argv) { 88 | // Create the heap used for long-lived stuff (that gets freed piecewise). 89 | PermHeap = HeapCreate(HEAP_NO_SERIALIZE, 1024*1024*20, 0); 90 | // Create the heap that we use to store Exprs and other temp stuff. 91 | FreeAllTemporary(); 92 | 93 | #if !defined(LIBRARY) && defined(_MSC_VER) 94 | // Don't display the abort message; it is aggravating in CLI binaries 95 | // and results in infinite WndProc recursion in GUI binaries. 96 | _set_abort_behavior(0, _WRITE_ABORT_MSG); 97 | int crtReportTypes[] = {_CRT_WARN, _CRT_ERROR, _CRT_ASSERT}; 98 | for(int crtReportType : crtReportTypes) { 99 | _CrtSetReportMode(crtReportType, _CRTDBG_MODE_FILE | _CRTDBG_MODE_DEBUG); 100 | _CrtSetReportFile(crtReportType, _CRTDBG_FILE_STDERR); 101 | } 102 | #endif 103 | 104 | // Extract the command-line arguments; the ones from main() are ignored, 105 | // since they are in the OEM encoding. 106 | int argcW; 107 | LPWSTR *argvW = CommandLineToArgvW(GetCommandLineW(), &argcW); 108 | std::vector args; 109 | for(int i = 0; i < argcW; i++) { 110 | args.push_back(Platform::Narrow(argvW[i])); 111 | } 112 | LocalFree(argvW); 113 | return args; 114 | } 115 | 116 | } 117 | -------------------------------------------------------------------------------- /src/polygon.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Anything relating to plane polygons and triangles, and (generally, non- 3 | // planar) meshes thereof. 4 | // 5 | // Copyright 2008-2013 Jonathan Westhues. 6 | //----------------------------------------------------------------------------- 7 | 8 | #ifndef __POLYGON_H 9 | #define __POLYGON_H 10 | 11 | class SPointList; 12 | class SPolygon; 13 | class SContour; 14 | class SMesh; 15 | class SBsp3; 16 | class SOutlineList; 17 | 18 | enum class EarType : uint32_t { 19 | UNKNOWN = 0, 20 | NOT_EAR = 1, 21 | EAR = 2 22 | }; 23 | 24 | enum class BspClass : uint32_t { 25 | POS = 100, 26 | NEG = 101, 27 | COPLANAR = 200 28 | }; 29 | 30 | enum class EdgeKind : uint32_t { 31 | NAKED_OR_SELF_INTER = 100, 32 | SELF_INTER = 200, 33 | TURNING = 300, 34 | EMPHASIZED = 400, 35 | SHARP = 500, 36 | }; 37 | 38 | class SEdge { 39 | public: 40 | int tag; 41 | int auxA, auxB; 42 | Vector a, b; 43 | 44 | static SEdge From(Vector a, Vector b); 45 | bool EdgeCrosses(Vector a, Vector b, Vector *pi=NULL, SPointList *spl=NULL) const; 46 | }; 47 | 48 | class SEdgeList { 49 | public: 50 | List l; 51 | 52 | void Clear(); 53 | void AddEdge(Vector a, Vector b, int auxA=0, int auxB=0, int tag=0); 54 | bool AssemblePolygon(SPolygon *dest, SEdge *errorAt, bool keepDir=false) const; 55 | bool AssembleContour(Vector first, Vector last, SContour *dest, 56 | SEdge *errorAt, bool keepDir) const; 57 | int AnyEdgeCrossings(Vector a, Vector b, 58 | Vector *pi=NULL, SPointList *spl=NULL) const; 59 | bool ContainsEdgeFrom(const SEdgeList *sel) const; 60 | bool ContainsEdge(const SEdge *se) const; 61 | void CullExtraneousEdges(); 62 | void MergeCollinearSegments(Vector a, Vector b); 63 | }; 64 | 65 | // A kd-tree element needs to go on a side of a node if it's when KDTREE_EPS 66 | // of the boundary. So increasing this number never breaks anything, but may 67 | // result in more duplicated elements. So it's conservative to be sloppy here. 68 | #define KDTREE_EPS (20*LENGTH_EPS) 69 | 70 | class SEdgeLl { 71 | public: 72 | SEdge *se; 73 | SEdgeLl *next; 74 | 75 | static SEdgeLl *Alloc(); 76 | }; 77 | 78 | class SKdNodeEdges { 79 | public: 80 | int which; // whether c is x, y, or z 81 | double c; 82 | SKdNodeEdges *gt; 83 | SKdNodeEdges *lt; 84 | 85 | SEdgeLl *edges; 86 | 87 | static SKdNodeEdges *From(SEdgeList *sel); 88 | static SKdNodeEdges *From(SEdgeLl *sell); 89 | static SKdNodeEdges *Alloc(); 90 | int AnyEdgeCrossings(Vector a, Vector b, int cnt, 91 | Vector *pi=NULL, SPointList *spl=NULL) const; 92 | }; 93 | 94 | class SPoint { 95 | public: 96 | int tag; 97 | 98 | EarType ear; 99 | 100 | Vector p; 101 | Vector auxv; 102 | }; 103 | 104 | class SPointList { 105 | public: 106 | List l; 107 | 108 | void Clear(); 109 | bool ContainsPoint(Vector pt) const; 110 | int IndexForPoint(Vector pt) const; 111 | void IncrementTagFor(Vector pt); 112 | void Add(Vector pt); 113 | }; 114 | 115 | class SContour { 116 | public: 117 | int tag; 118 | int timesEnclosed; 119 | Vector xminPt; 120 | List l; 121 | 122 | void AddPoint(Vector p); 123 | void MakeEdgesInto(SEdgeList *el) const; 124 | void Reverse(); 125 | Vector ComputeNormal() const; 126 | double SignedAreaProjdToNormal(Vector n) const; 127 | bool IsClockwiseProjdToNormal(Vector n) const; 128 | bool ContainsPointProjdToNormal(Vector n, Vector p) const; 129 | void OffsetInto(SContour *dest, double r) const; 130 | void CopyInto(SContour *dest) const; 131 | void FindPointWithMinX(); 132 | Vector AnyEdgeMidpoint() const; 133 | 134 | bool IsEar(int bp, double scaledEps) const; 135 | bool BridgeToContour(SContour *sc, SEdgeList *el, List *vl); 136 | void ClipEarInto(SMesh *m, int bp, double scaledEps); 137 | void UvTriangulateInto(SMesh *m, SSurface *srf); 138 | }; 139 | 140 | typedef struct { 141 | uint32_t face; 142 | RgbaColor color; 143 | } STriMeta; 144 | 145 | class SPolygon { 146 | public: 147 | List l; 148 | Vector normal; 149 | 150 | Vector ComputeNormal() const; 151 | void AddEmptyContour(); 152 | int WindingNumberForPoint(Vector p) const; 153 | double SignedArea() const; 154 | bool ContainsPoint(Vector p) const; 155 | void MakeEdgesInto(SEdgeList *el) const; 156 | void FixContourDirections(); 157 | void Clear(); 158 | bool SelfIntersecting(Vector *intersectsAt) const; 159 | bool IsEmpty() const; 160 | Vector AnyPoint() const; 161 | void OffsetInto(SPolygon *dest, double r) const; 162 | void UvTriangulateInto(SMesh *m, SSurface *srf); 163 | void UvGridTriangulateInto(SMesh *m, SSurface *srf); 164 | void TriangulateInto(SMesh *m) const; 165 | void InverseTransformInto(SPolygon *sp, Vector u, Vector v, Vector n) const; 166 | }; 167 | 168 | class STriangle { 169 | public: 170 | int tag; 171 | STriMeta meta; 172 | 173 | union { 174 | struct { Vector a, b, c; }; 175 | Vector vertices[3]; 176 | }; 177 | 178 | union { 179 | struct { Vector an, bn, cn; }; 180 | Vector normals[3]; 181 | }; 182 | 183 | static STriangle From(STriMeta meta, Vector a, Vector b, Vector c); 184 | Vector Normal() const; 185 | void FlipNormal(); 186 | double MinAltitude() const; 187 | int WindingNumberForPoint(Vector p) const; 188 | bool ContainsPoint(Vector p) const; 189 | bool ContainsPointProjd(Vector n, Vector p) const; 190 | STriangle Transform(Vector o, Vector u, Vector v) const; 191 | bool Raytrace(const Vector &rayPoint, const Vector &rayDir, 192 | double *t, Vector *inters) const; 193 | double SignedVolume() const; 194 | }; 195 | 196 | class SBsp2 { 197 | public: 198 | Vector np; // normal to the plane 199 | 200 | Vector no; // outer normal to the edge 201 | double d; 202 | SEdge edge; 203 | 204 | SBsp2 *pos; 205 | SBsp2 *neg; 206 | 207 | SBsp2 *more; 208 | 209 | void InsertTriangleHow(BspClass how, STriangle *tr, SMesh *m, SBsp3 *bsp3); 210 | void InsertTriangle(STriangle *tr, SMesh *m, SBsp3 *bsp3); 211 | Vector IntersectionWith(Vector a, Vector b) const; 212 | void InsertEdge(SEdge *nedge, Vector nnp, Vector out); 213 | static SBsp2 *InsertOrCreateEdge(SBsp2 *where, SEdge *nedge, 214 | Vector nnp, Vector out); 215 | static SBsp2 *Alloc(); 216 | }; 217 | 218 | class SBsp3 { 219 | public: 220 | Vector n; 221 | double d; 222 | 223 | STriangle tri; 224 | SBsp3 *pos; 225 | SBsp3 *neg; 226 | 227 | SBsp3 *more; 228 | 229 | SBsp2 *edges; 230 | 231 | static SBsp3 *Alloc(); 232 | static SBsp3 *FromMesh(const SMesh *m); 233 | 234 | Vector IntersectionWith(Vector a, Vector b) const; 235 | 236 | void InsertHow(BspClass how, STriangle *str, SMesh *instead); 237 | void Insert(STriangle *str, SMesh *instead); 238 | static SBsp3 *InsertOrCreate(SBsp3 *where, STriangle *str, SMesh *instead); 239 | 240 | void InsertConvexHow(BspClass how, STriMeta meta, Vector *vertex, size_t n, 241 | SMesh *instead); 242 | SBsp3 *InsertConvex(STriMeta meta, Vector *vertex, size_t n, SMesh *instead); 243 | 244 | void InsertInPlane(bool pos2, STriangle *tr, SMesh *m); 245 | 246 | void GenerateInPaintOrder(SMesh *m) const; 247 | }; 248 | 249 | class SMesh { 250 | public: 251 | List l; 252 | 253 | bool flipNormal; 254 | bool keepCoplanar; 255 | bool atLeastOneDiscarded; 256 | bool isTransparent; 257 | 258 | void Clear(); 259 | void AddTriangle(const STriangle *st); 260 | void AddTriangle(STriMeta meta, Vector a, Vector b, Vector c); 261 | void AddTriangle(STriMeta meta, Vector n, 262 | Vector a, Vector b, Vector c); 263 | void DoBounding(Vector v, Vector *vmax, Vector *vmin) const; 264 | void GetBounding(Vector *vmax, Vector *vmin) const; 265 | 266 | void Simplify(int start); 267 | 268 | void AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3); 269 | void MakeFromUnionOf(SMesh *a, SMesh *b); 270 | void MakeFromDifferenceOf(SMesh *a, SMesh *b); 271 | 272 | void MakeFromCopyOf(SMesh *a); 273 | void MakeFromTransformationOf(SMesh *a, Vector trans, 274 | Quaternion q, double scale); 275 | void MakeFromAssemblyOf(SMesh *a, SMesh *b); 276 | 277 | void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d); 278 | void MakeOutlinesInto(SOutlineList *sol, EdgeKind type); 279 | 280 | void PrecomputeTransparency(); 281 | void RemoveDegenerateTriangles(); 282 | 283 | bool IsEmpty() const; 284 | void RemapFaces(Group *g, int remap); 285 | 286 | uint32_t FirstIntersectionWith(Point2d mp) const; 287 | 288 | Vector GetCenterOfMass() const; 289 | }; 290 | 291 | // A linked list of triangles 292 | class STriangleLl { 293 | public: 294 | STriangle *tri; 295 | 296 | STriangleLl *next; 297 | 298 | static STriangleLl *Alloc(); 299 | }; 300 | 301 | class SOutline { 302 | public: 303 | int tag; 304 | Vector a, b, nl, nr; 305 | 306 | bool IsVisible(Vector projDir) const; 307 | }; 308 | 309 | class SOutlineList { 310 | public: 311 | List l; 312 | 313 | void Clear(); 314 | void AddEdge(Vector a, Vector b, Vector nl, Vector nr, int tag = 0); 315 | void ListTaggedInto(SEdgeList *el, int auxA = 0, int auxB = 0); 316 | 317 | void MakeFromCopyOf(SOutlineList *ol); 318 | }; 319 | 320 | class SKdNode { 321 | public: 322 | struct EdgeOnInfo { 323 | int count; 324 | bool frontFacing; 325 | bool intersectsMesh; 326 | STriangle *tr; 327 | int ai; 328 | int bi; 329 | }; 330 | 331 | int which; // whether c is x, y, or z 332 | double c; 333 | 334 | SKdNode *gt; 335 | SKdNode *lt; 336 | 337 | STriangleLl *tris; 338 | 339 | static SKdNode *Alloc(); 340 | static SKdNode *From(SMesh *m); 341 | static SKdNode *From(STriangleLl *tll); 342 | 343 | void AddTriangle(STriangle *tr); 344 | void MakeMeshInto(SMesh *m) const; 345 | void ListTrianglesInto(std::vector *tl) const; 346 | void ClearTags() const; 347 | 348 | void FindEdgeOn(Vector a, Vector b, int cnt, bool coplanarIsInter, EdgeOnInfo *info) const; 349 | void MakeCertainEdgesInto(SEdgeList *sel, EdgeKind how, bool coplanarIsInter, 350 | bool *inter, bool *leaky, int auxA = 0) const; 351 | void MakeOutlinesInto(SOutlineList *sel, EdgeKind tagKind) const; 352 | 353 | void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) const; 354 | void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) const; 355 | 356 | void SnapToMesh(SMesh *m); 357 | void SnapToVertex(Vector v, SMesh *extras); 358 | }; 359 | 360 | class PolylineBuilder { 361 | public: 362 | struct Edge; 363 | 364 | struct Vertex { 365 | Vector pos; 366 | std::vector edges; 367 | 368 | bool GetNext(uint32_t kind, Vertex **next, Edge **nextEdge); 369 | bool GetNext(uint32_t kind, Vector plane, double d, Vertex **next, Edge **nextEdge); 370 | size_t CountEdgesWithTagAndKind(int tag, uint32_t kind) const; 371 | }; 372 | 373 | struct VertexPairHash { 374 | size_t operator()(const std::pair &v) const; 375 | }; 376 | 377 | struct Edge { 378 | Vertex *a; 379 | Vertex *b; 380 | uint32_t kind; 381 | int tag; 382 | 383 | union { 384 | uintptr_t data; 385 | SOutline *outline; 386 | SEdge *edge; 387 | }; 388 | 389 | Vertex *GetOtherVertex(Vertex *v) const; 390 | bool GetStartAndNext(Vertex **start, Vertex **next, bool loop) const; 391 | }; 392 | 393 | std::unordered_map vertices; 394 | std::unordered_map, Edge *, VertexPairHash> edgeMap; 395 | std::vector edges; 396 | 397 | ~PolylineBuilder(); 398 | void Clear(); 399 | 400 | Vertex *AddVertex(const Vector &pos); 401 | Edge *AddEdge(const Vector &p0, const Vector &p1, uint32_t kind, uintptr_t data = 0); 402 | void Generate( 403 | std::function startFunc, 404 | std::function nextFunc, 405 | std::function aloneFunc, 406 | std::function endFunc = [](){}); 407 | 408 | void MakeFromEdges(const SEdgeList &sel); 409 | void MakeFromOutlines(const SOutlineList &sol); 410 | void GenerateEdges(SEdgeList *sel); 411 | void GenerateOutlines(SOutlineList *sol); 412 | }; 413 | 414 | #endif 415 | 416 | -------------------------------------------------------------------------------- /src/render/render.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Backend-agnostic rendering interface, and various backends we use. 3 | // 4 | // Copyright 2016 whitequark 5 | //----------------------------------------------------------------------------- 6 | 7 | #ifndef SOLVESPACE_RENDER_H 8 | #define SOLVESPACE_RENDER_H 9 | 10 | //----------------------------------------------------------------------------- 11 | // Interfaces and utilities common for all renderers. 12 | //----------------------------------------------------------------------------- 13 | 14 | enum class StipplePattern : uint32_t; 15 | 16 | // A mapping from 3d sketch coordinates to 2d screen coordinates, using 17 | // an axonometric projection. 18 | class Camera { 19 | public: 20 | size_t width, height; 21 | Vector offset; 22 | Vector projRight; 23 | Vector projUp; 24 | double scale; 25 | double tangent; 26 | bool hasPixels; 27 | 28 | bool IsPerspective() const { return tangent != 0.0; } 29 | 30 | Point2d ProjectPoint(Vector p) const; 31 | Vector ProjectPoint3(Vector p) const; 32 | Vector ProjectPoint4(Vector p, double *w) const; 33 | Vector UnProjectPoint(Point2d p) const; 34 | Vector UnProjectPoint3(Vector p) const; 35 | Vector VectorFromProjs(Vector rightUpForward) const; 36 | Vector AlignToPixelGrid(Vector v) const; 37 | 38 | SBezier ProjectBezier(SBezier b) const; 39 | 40 | void LoadIdentity(); 41 | void NormalizeProjectionVectors(); 42 | }; 43 | 44 | // A description of scene lighting. 45 | class Lighting { 46 | public: 47 | RgbaColor backgroundColor; 48 | double ambientIntensity; 49 | double lightIntensity[2]; 50 | Vector lightDirection[2]; 51 | }; 52 | 53 | class BatchCanvas; 54 | 55 | // An interface for populating a drawing area with geometry. 56 | class Canvas { 57 | public: 58 | // Stroke and fill styles are addressed with handles to be able to quickly 59 | // group geometry into indexed draw calls. 60 | class hStroke { 61 | public: 62 | uint32_t v; 63 | }; 64 | 65 | class hFill { 66 | public: 67 | uint32_t v; 68 | }; 69 | 70 | // The layer of a geometry describes how it occludes other geometry. 71 | // Within a layer, geometry with higher z-index occludes geometry with lower z-index, 72 | // or geometry drawn earlier if z-indexes match. 73 | enum class Layer { 74 | NORMAL, // Occluded by geometry with lower Z coordinate 75 | OCCLUDED, // Only drawn over geometry with lower Z coordinate 76 | DEPTH_ONLY, // Like NORMAL, but only affects future occlusion, not color 77 | BACK, // Always drawn below all other geometry 78 | FRONT, // Always drawn above all other geometry 79 | LAST = FRONT 80 | }; 81 | 82 | // The outlines are the collection of all edges that may be drawn. 83 | // Outlines can be classified as emphasized or not; emphasized outlines indicate an abrupt 84 | // change in the surface curvature. These are indicated by the SOutline tag. 85 | // Outlines can also be classified as contour or not; contour outlines indicate the boundary 86 | // of the filled mesh. Whether an outline is a part of contour or not depends on point of view. 87 | enum class DrawOutlinesAs { 88 | EMPHASIZED_AND_CONTOUR = 0, // Both emphasized and contour outlines 89 | EMPHASIZED_WITHOUT_CONTOUR = 1, // Emphasized outlines except those also belonging to contour 90 | CONTOUR_ONLY = 2 // Contour outlines only 91 | }; 92 | 93 | // Stroke widths, etc, can be scale-invariant (in pixels) or scale-dependent (in millimeters). 94 | enum class Unit { 95 | MM, 96 | PX 97 | }; 98 | 99 | class Stroke { 100 | public: 101 | hStroke h; 102 | 103 | Layer layer; 104 | int zIndex; 105 | RgbaColor color; 106 | double width; 107 | Unit unit; 108 | StipplePattern stipplePattern; 109 | double stippleScale; 110 | 111 | void Clear() { *this = {}; } 112 | bool Equals(const Stroke &other) const; 113 | 114 | double WidthMm(const Camera &camera) const; 115 | double WidthPx(const Camera &camera) const; 116 | double StippleScaleMm(const Camera &camera) const; 117 | double StippleScalePx(const Camera &camera) const; 118 | }; 119 | 120 | enum class FillPattern { 121 | SOLID, CHECKERED_A, CHECKERED_B 122 | }; 123 | 124 | class Fill { 125 | public: 126 | hFill h; 127 | 128 | Layer layer; 129 | int zIndex; 130 | RgbaColor color; 131 | FillPattern pattern; 132 | std::shared_ptr texture; 133 | 134 | void Clear() { *this = {}; } 135 | bool Equals(const Fill &other) const; 136 | }; 137 | 138 | IdList strokes; 139 | IdList fills; 140 | BitmapFont bitmapFont; 141 | 142 | Canvas() : strokes(), fills(), bitmapFont() {} 143 | virtual void Clear(); 144 | 145 | hStroke GetStroke(const Stroke &stroke); 146 | hFill GetFill(const Fill &fill); 147 | BitmapFont *GetBitmapFont(); 148 | 149 | virtual const Camera &GetCamera() const = 0; 150 | 151 | virtual void DrawLine(const Vector &a, const Vector &b, hStroke hcs) = 0; 152 | virtual void DrawEdges(const SEdgeList &el, hStroke hcs) = 0; 153 | virtual bool DrawBeziers(const SBezierList &bl, hStroke hcs) = 0; 154 | virtual void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) = 0; 155 | virtual void DrawVectorText(const std::string &text, double height, 156 | const Vector &o, const Vector &u, const Vector &v, 157 | hStroke hcs) = 0; 158 | 159 | virtual void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, 160 | hFill hcf) = 0; 161 | virtual void DrawPoint(const Vector &o, hStroke hcs) = 0; 162 | virtual void DrawPolygon(const SPolygon &p, hFill hcf) = 0; 163 | virtual void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack = {}) = 0; 164 | virtual void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) = 0; 165 | 166 | virtual void DrawPixmap(std::shared_ptr pm, 167 | const Vector &o, const Vector &u, const Vector &v, 168 | const Point2d &ta, const Point2d &tb, hFill hcf) = 0; 169 | virtual void InvalidatePixmap(std::shared_ptr pm) = 0; 170 | 171 | virtual std::shared_ptr CreateBatch(); 172 | }; 173 | 174 | // An interface for view-dependent visualization. 175 | class ViewportCanvas : public Canvas { 176 | public: 177 | virtual void SetCamera(const Camera &camera) = 0; 178 | virtual void SetLighting(const Lighting &lighting) = 0; 179 | 180 | virtual void NewFrame() = 0; 181 | virtual void FlushFrame() = 0; 182 | virtual std::shared_ptr ReadFrame() = 0; 183 | 184 | virtual void GetIdent(const char **vendor, const char **renderer, const char **version) = 0; 185 | }; 186 | 187 | // An interface for view-independent visualization. 188 | class BatchCanvas : public Canvas { 189 | public: 190 | const Camera &GetCamera() const override; 191 | 192 | virtual void Finalize() = 0; 193 | virtual void Draw() = 0; 194 | }; 195 | 196 | // A wrapper around Canvas that simplifies drawing UI in screen coordinates. 197 | class UiCanvas { 198 | public: 199 | std::shared_ptr canvas; 200 | bool flip; 201 | 202 | void DrawLine(int x1, int y1, int x2, int y2, RgbaColor color, int width = 1, 203 | int zIndex = 0); 204 | void DrawRect(int l, int r, int t, int b, RgbaColor fillColor, RgbaColor outlineColor, 205 | int zIndex = 0); 206 | void DrawPixmap(std::shared_ptr pm, int x, int y, 207 | int zIndex = 0); 208 | void DrawBitmapChar(char32_t codepoint, int x, int y, RgbaColor color, 209 | int zIndex = 0); 210 | void DrawBitmapText(const std::string &str, int x, int y, RgbaColor color, 211 | int zIndex = 0); 212 | 213 | int Flip(int y) const { return flip ? (int)canvas->GetCamera().height - y : y; } 214 | }; 215 | 216 | // A canvas that performs picking against drawn geometry. 217 | class ObjectPicker : public Canvas { 218 | public: 219 | Camera camera; 220 | // Configuration. 221 | Point2d point; 222 | double selRadius; 223 | // Picking state. 224 | double minDistance; 225 | int maxZIndex; 226 | uint32_t position; 227 | 228 | ObjectPicker() : camera(), point(), selRadius(), 229 | minDistance(), maxZIndex(), position() {} 230 | 231 | const Camera &GetCamera() const override { return camera; } 232 | 233 | void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; 234 | void DrawEdges(const SEdgeList &el, hStroke hcs) override; 235 | bool DrawBeziers(const SBezierList &bl, hStroke hcs) override { return false; } 236 | void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override; 237 | void DrawVectorText(const std::string &text, double height, 238 | const Vector &o, const Vector &u, const Vector &v, 239 | hStroke hcs) override; 240 | 241 | void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, 242 | hFill hcf) override; 243 | void DrawPoint(const Vector &o, hStroke hcs) override; 244 | void DrawPolygon(const SPolygon &p, hFill hcf) override; 245 | void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) override; 246 | void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override; 247 | 248 | void DrawPixmap(std::shared_ptr pm, 249 | const Vector &o, const Vector &u, const Vector &v, 250 | const Point2d &ta, const Point2d &tb, hFill hcf) override; 251 | void InvalidatePixmap(std::shared_ptr pm) override {} 252 | 253 | void DoCompare(double distance, int zIndex, int comparePosition = 0); 254 | void DoQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, 255 | int zIndex, int comparePosition = 0); 256 | 257 | bool Pick(std::function drawFn); 258 | }; 259 | 260 | // A canvas that renders onto a 2d surface, performing z-index sorting, occlusion testing, etc, 261 | // on the CPU. 262 | class SurfaceRenderer : public Canvas { 263 | public: 264 | Camera camera; 265 | Lighting lighting; 266 | // Chord tolerance, for converting beziers to pwl. 267 | double chordTolerance; 268 | // Render lists. 269 | handle_map edges; 270 | handle_map beziers; 271 | SMesh mesh; 272 | // State. 273 | BBox bbox; 274 | 275 | SurfaceRenderer() : camera(), lighting(), chordTolerance(), mesh(), bbox() {} 276 | void Clear() override; 277 | 278 | // Canvas interface. 279 | const Camera &GetCamera() const override { return camera; } 280 | 281 | void DrawLine(const Vector &a, const Vector &b, hStroke hcs) override; 282 | void DrawEdges(const SEdgeList &el, hStroke hcs) override; 283 | bool DrawBeziers(const SBezierList &bl, hStroke hcs) override; 284 | void DrawOutlines(const SOutlineList &ol, hStroke hcs, DrawOutlinesAs drawAs) override; 285 | void DrawVectorText(const std::string &text, double height, 286 | const Vector &o, const Vector &u, const Vector &v, 287 | hStroke hcs) override; 288 | 289 | void DrawQuad(const Vector &a, const Vector &b, const Vector &c, const Vector &d, 290 | hFill hcf) override; 291 | void DrawPoint(const Vector &o, hStroke hcs) override; 292 | void DrawPolygon(const SPolygon &p, hFill hcf) override; 293 | void DrawMesh(const SMesh &m, hFill hcfFront, hFill hcfBack) override; 294 | void DrawFaces(const SMesh &m, const std::vector &faces, hFill hcf) override; 295 | 296 | void DrawPixmap(std::shared_ptr pm, 297 | const Vector &o, const Vector &u, const Vector &v, 298 | const Point2d &ta, const Point2d &tb, hFill hcf) override; 299 | void InvalidatePixmap(std::shared_ptr pm) override; 300 | 301 | // Geometry manipulation. 302 | void CalculateBBox(); 303 | void ConvertBeziersToEdges(); 304 | void CullOccludedStrokes(); 305 | 306 | // Renderer operations. 307 | void OutputInPaintOrder(); 308 | 309 | virtual bool CanOutputCurves() const = 0; 310 | virtual bool CanOutputTriangles() const = 0; 311 | 312 | virtual void OutputStart() = 0; 313 | virtual void OutputBezier(const SBezier &b, hStroke hcs) = 0; 314 | virtual void OutputTriangle(const STriangle &tr) = 0; 315 | virtual void OutputEnd() = 0; 316 | 317 | void OutputBezierAsNonrationalCubic(const SBezier &b, hStroke hcs); 318 | }; 319 | 320 | //----------------------------------------------------------------------------- 321 | // 2d renderers. 322 | //----------------------------------------------------------------------------- 323 | 324 | class CairoRenderer : public SurfaceRenderer { 325 | public: 326 | cairo_t *context; 327 | // Renderer configuration. 328 | bool antialias; 329 | // Renderer state. 330 | struct { 331 | hStroke hcs; 332 | } current; 333 | 334 | CairoRenderer() : context(), current() {} 335 | 336 | void SelectStroke(hStroke hcs); 337 | void MoveTo(Vector p); 338 | void FinishPath(); 339 | 340 | bool CanOutputCurves() const override { return true; } 341 | bool CanOutputTriangles() const override { return true; } 342 | 343 | void OutputStart() override; 344 | void OutputBezier(const SBezier &b, hStroke hcs) override; 345 | void OutputTriangle(const STriangle &tr) override; 346 | void OutputEnd() override; 347 | }; 348 | 349 | //----------------------------------------------------------------------------- 350 | // 3d renderers. 351 | //----------------------------------------------------------------------------- 352 | 353 | // An offscreen renderer based on OpenGL framebuffers. 354 | class GlOffscreen { 355 | public: 356 | unsigned int framebuffer; 357 | unsigned int colorRenderbuffer, depthRenderbuffer; 358 | std::vector data; 359 | 360 | bool Render(int width, int height, std::function renderFn); 361 | void Clear(); 362 | }; 363 | 364 | std::shared_ptr CreateRenderer(); 365 | 366 | #endif 367 | -------------------------------------------------------------------------------- /src/resource.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Discovery and loading of our resources (icons, fonts, templates, etc). 3 | // 4 | // Copyright 2016 whitequark 5 | //----------------------------------------------------------------------------- 6 | 7 | #ifndef __RESOURCE_H 8 | #define __RESOURCE_H 9 | 10 | class Camera; 11 | class Point2d; 12 | class Pixmap; 13 | class Vector; 14 | 15 | std::string LoadString(const std::string &name); 16 | std::string LoadStringFromGzip(const std::string &name); 17 | std::shared_ptr LoadPng(const std::string &name); 18 | 19 | class Pixmap { 20 | public: 21 | enum class Format { BGRA, RGBA, BGR, RGB, A }; 22 | 23 | Format format; 24 | size_t width; 25 | size_t height; 26 | size_t stride; 27 | std::vector data; 28 | 29 | static std::shared_ptr Create(Format format, size_t width, size_t height); 30 | static std::shared_ptr FromPng(const uint8_t *data, size_t size, bool flip = false); 31 | 32 | static std::shared_ptr ReadPng(FILE *f, bool flip = false); 33 | static std::shared_ptr ReadPng(const Platform::Path &filename, bool flip = false); 34 | bool WritePng(FILE *f, bool flip = false); 35 | bool WritePng(const Platform::Path &filename, bool flip = false); 36 | 37 | size_t GetBytesPerPixel() const; 38 | RgbaColor GetPixel(size_t x, size_t y) const; 39 | bool Equals(const Pixmap &other) const; 40 | 41 | void ConvertTo(Format newFormat); 42 | void SetPixel(size_t x, size_t y, RgbaColor color); 43 | }; 44 | 45 | class BitmapFont { 46 | public: 47 | struct Glyph { 48 | uint8_t advanceCells; 49 | uint16_t position; 50 | }; 51 | 52 | std::string unifontData; 53 | std::map glyphs; 54 | std::shared_ptr texture; 55 | bool textureUpdated; 56 | uint16_t nextPosition; 57 | 58 | static BitmapFont From(std::string &&unifontData); 59 | static BitmapFont Create(); 60 | 61 | bool IsEmpty() const { return unifontData.empty(); } 62 | const Glyph &GetGlyph(char32_t codepoint); 63 | void LocateGlyph(char32_t codepoint, double *s0, double *t0, double *s1, double *t1, 64 | size_t *advanceWidth, size_t *boundingHeight); 65 | 66 | void AddGlyph(char32_t codepoint, std::shared_ptr pixmap); 67 | 68 | size_t GetWidth(char32_t codepoint); 69 | size_t GetWidth(const std::string &str); 70 | }; 71 | 72 | class VectorFont { 73 | public: 74 | struct Contour { 75 | std::vector points; 76 | }; 77 | 78 | struct Glyph { 79 | std::vector contours; 80 | double leftSideBearing; 81 | double boundingWidth; 82 | double advanceWidth; 83 | }; 84 | 85 | std::string lffData; 86 | std::map glyphs; 87 | double rightSideBearing; 88 | double capHeight; 89 | double ascender; 90 | double descender; 91 | 92 | static VectorFont From(std::string &&lffData); 93 | static VectorFont *Builtin(); 94 | 95 | bool IsEmpty() const { return lffData.empty(); } 96 | const Glyph &GetGlyph(char32_t codepoint); 97 | 98 | double GetCapHeight(double forCapHeight) const; 99 | double GetHeight(double forCapHeight) const; 100 | double GetWidth(double forCapHeight, const std::string &str); 101 | Vector GetExtents(double forCapHeight, const std::string &str); 102 | 103 | void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, 104 | std::function traceEdge); 105 | void Trace(double forCapHeight, Vector o, Vector u, Vector v, const std::string &str, 106 | std::function traceEdge, const Camera &camera); 107 | }; 108 | 109 | #endif 110 | -------------------------------------------------------------------------------- /src/srf/surface.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Functions relating to rational polynomial surfaces, which are trimmed by 3 | // curves (either rational polynomial curves, or piecewise linear 4 | // approximations to curves of intersection that can't be represented 5 | // exactly in ratpoly form), and assembled into watertight shells. 6 | // 7 | // Copyright 2008-2013 Jonathan Westhues. 8 | //----------------------------------------------------------------------------- 9 | 10 | #ifndef __SURFACE_H 11 | #define __SURFACE_H 12 | 13 | // Utility functions, Bernstein polynomials of order 1-3 and their derivatives. 14 | double Bernstein(int k, int deg, double t); 15 | double BernsteinDerivative(int k, int deg, double t); 16 | 17 | class SBezierList; 18 | class SSurface; 19 | class SCurvePt; 20 | 21 | // Utility data structure, a two-dimensional BSP to accelerate polygon 22 | // operations. 23 | class SBspUv { 24 | public: 25 | Point2d a, b; 26 | 27 | SBspUv *pos; 28 | SBspUv *neg; 29 | 30 | SBspUv *more; 31 | 32 | enum class Class : uint32_t { 33 | INSIDE = 100, 34 | OUTSIDE = 200, 35 | EDGE_PARALLEL = 300, 36 | EDGE_ANTIPARALLEL = 400, 37 | EDGE_OTHER = 500 38 | }; 39 | 40 | static SBspUv *Alloc(); 41 | static SBspUv *From(SEdgeList *el, SSurface *srf); 42 | 43 | void ScalePoints(Point2d *pt, Point2d *a, Point2d *b, SSurface *srf) const; 44 | double ScaledSignedDistanceToLine(Point2d pt, Point2d a, Point2d b, 45 | SSurface *srf) const; 46 | double ScaledDistanceToLine(Point2d pt, Point2d a, Point2d b, bool asSegment, 47 | SSurface *srf) const; 48 | 49 | void InsertEdge(Point2d a, Point2d b, SSurface *srf); 50 | static SBspUv *InsertOrCreateEdge(SBspUv *where, Point2d ea, Point2d eb, SSurface *srf); 51 | Class ClassifyPoint(Point2d p, Point2d eb, SSurface *srf) const; 52 | Class ClassifyEdge(Point2d ea, Point2d eb, SSurface *srf) const; 53 | double MinimumDistanceToEdge(Point2d p, SSurface *srf) const; 54 | }; 55 | 56 | // Now the data structures to represent a shell of trimmed rational polynomial 57 | // surfaces. 58 | 59 | class SShell; 60 | 61 | class hSSurface { 62 | public: 63 | uint32_t v; 64 | }; 65 | 66 | class hSCurve { 67 | public: 68 | uint32_t v; 69 | }; 70 | 71 | // Stuff for rational polynomial curves, of degree one to three. These are 72 | // our inputs, and are also calculated for certain exact surface-surface 73 | // intersections. 74 | class SBezier { 75 | public: 76 | int tag; 77 | int auxA, auxB; 78 | 79 | int deg; 80 | Vector ctrl[4]; 81 | double weight[4]; 82 | uint32_t entity; 83 | 84 | Vector PointAt(double t) const; 85 | Vector TangentAt(double t) const; 86 | void ClosestPointTo(Vector p, double *t, bool mustConverge=true) const; 87 | void SplitAt(double t, SBezier *bef, SBezier *aft) const; 88 | bool PointOnThisAndCurve(const SBezier *sbb, Vector *p) const; 89 | 90 | Vector Start() const; 91 | Vector Finish() const; 92 | bool Equals(SBezier *b) const; 93 | void MakePwlInto(SEdgeList *sel, double chordTol=0) const; 94 | void MakePwlInto(List *l, double chordTol=0) const; 95 | void MakePwlInto(SContour *sc, double chordTol=0) const; 96 | void MakePwlInto(List *l, double chordTol=0) const; 97 | void MakePwlWorker(List *l, double ta, double tb, double chordTol) const; 98 | void MakePwlInitialWorker(List *l, double ta, double tb, double chordTol) const; 99 | void MakeNonrationalCubicInto(SBezierList *bl, double tolerance, int depth = 0) const; 100 | 101 | void AllIntersectionsWith(const SBezier *sbb, SPointList *spl) const; 102 | void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const; 103 | void Reverse(); 104 | 105 | bool IsInPlane(Vector n, double d) const; 106 | bool IsCircle(Vector axis, Vector *center, double *r) const; 107 | bool IsRational() const; 108 | 109 | SBezier TransformedBy(Vector t, Quaternion q, double scale) const; 110 | SBezier InPerspective(Vector u, Vector v, Vector n, 111 | Vector origin, double cameraTan) const; 112 | void ScaleSelfBy(double s); 113 | 114 | static SBezier From(Vector p0, Vector p1, Vector p2, Vector p3); 115 | static SBezier From(Vector p0, Vector p1, Vector p2); 116 | static SBezier From(Vector p0, Vector p1); 117 | static SBezier From(Vector4 p0, Vector4 p1, Vector4 p2, Vector4 p3); 118 | static SBezier From(Vector4 p0, Vector4 p1, Vector4 p2); 119 | static SBezier From(Vector4 p0, Vector4 p1); 120 | }; 121 | 122 | class SBezierList { 123 | public: 124 | List l; 125 | 126 | void Clear(); 127 | void ScaleSelfBy(double s); 128 | void CullIdenticalBeziers(); 129 | void AllIntersectionsWith(SBezierList *sblb, SPointList *spl) const; 130 | bool GetPlaneContainingBeziers(Vector *p, Vector *u, Vector *v, 131 | Vector *notCoplanarAt) const; 132 | }; 133 | 134 | class SBezierLoop { 135 | public: 136 | int tag; 137 | List l; 138 | 139 | inline void Clear() { l.Clear(); } 140 | bool IsClosed() const; 141 | void Reverse(); 142 | void MakePwlInto(SContour *sc, double chordTol=0) const; 143 | void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const; 144 | 145 | static SBezierLoop FromCurves(SBezierList *spcl, 146 | bool *allClosed, SEdge *errorAt); 147 | }; 148 | 149 | class SBezierLoopSet { 150 | public: 151 | List l; 152 | Vector normal; 153 | Vector point; 154 | double area; 155 | 156 | static SBezierLoopSet From(SBezierList *spcl, SPolygon *poly, 157 | double chordTol, 158 | bool *allClosed, SEdge *errorAt, 159 | SBezierList *openContours); 160 | 161 | void GetBoundingProjd(Vector u, Vector orig, double *umin, double *umax) const; 162 | double SignedArea(); 163 | void MakePwlInto(SPolygon *sp) const; 164 | void Clear(); 165 | }; 166 | 167 | class SBezierLoopSetSet { 168 | public: 169 | List l; 170 | 171 | void FindOuterFacesFrom(SBezierList *sbl, SPolygon *spxyz, SSurface *srfuv, 172 | double chordTol, 173 | bool *allClosed, SEdge *notClosedAt, 174 | bool *allCoplanar, Vector *notCoplanarAt, 175 | SBezierList *openContours); 176 | void AddOpenPath(SBezier *sb); 177 | void Clear(); 178 | }; 179 | 180 | // Stuff for the surface trim curves: piecewise linear 181 | class SCurvePt { 182 | public: 183 | int tag; 184 | Vector p; 185 | bool vertex; 186 | }; 187 | 188 | class SCurve { 189 | public: 190 | hSCurve h; 191 | 192 | // In a Boolean, C = A op B. The curves in A and B get copied into C, and 193 | // therefore must get new hSCurves assigned. For the curves in A and B, 194 | // we use newH to record their new handle in C. 195 | hSCurve newH; 196 | enum class Source : uint32_t { 197 | A = 100, 198 | B = 200, 199 | INTERSECTION = 300 200 | }; 201 | Source source; 202 | 203 | bool isExact; 204 | SBezier exact; 205 | 206 | List pts; 207 | 208 | hSSurface surfA; 209 | hSSurface surfB; 210 | 211 | static SCurve FromTransformationOf(SCurve *a, Vector t, 212 | Quaternion q, double scale); 213 | SCurve MakeCopySplitAgainst(SShell *agnstA, SShell *agnstB, 214 | SSurface *srfA, SSurface *srfB) const; 215 | void RemoveShortSegments(SSurface *srfA, SSurface *srfB); 216 | SSurface *GetSurfaceA(SShell *a, SShell *b) const; 217 | SSurface *GetSurfaceB(SShell *a, SShell *b) const; 218 | 219 | void Clear(); 220 | }; 221 | 222 | // A segment of a curve by which a surface is trimmed: indicates which curve, 223 | // by its handle, and the starting and ending points of our segment of it. 224 | // The vector out points out of the surface; it, the surface outer normal, 225 | // and a tangent to the beginning of the curve are all orthogonal. 226 | class STrimBy { 227 | public: 228 | hSCurve curve; 229 | bool backwards; 230 | // If a trim runs backwards, then start and finish still correspond to 231 | // the actual start and finish, but they appear in reverse order in 232 | // the referenced curve. 233 | Vector start; 234 | Vector finish; 235 | 236 | static STrimBy EntireCurve(SShell *shell, hSCurve hsc, bool backwards); 237 | }; 238 | 239 | // An intersection point between a line and a surface 240 | class SInter { 241 | public: 242 | int tag; 243 | Vector p; 244 | SSurface *srf; 245 | Point2d pinter; 246 | Vector surfNormal; // of the intersecting surface, at pinter 247 | bool onEdge; // pinter is on edge of trim poly 248 | }; 249 | 250 | // A rational polynomial surface in Bezier form. 251 | class SSurface { 252 | public: 253 | 254 | enum class CombineAs : uint32_t { 255 | UNION = 10, 256 | DIFFERENCE = 11, 257 | INTERSECT = 12 258 | }; 259 | 260 | int tag; 261 | hSSurface h; 262 | 263 | // Same as newH for the curves; record what a surface gets renamed to 264 | // when I copy things over. 265 | hSSurface newH; 266 | 267 | RgbaColor color; 268 | uint32_t face; 269 | 270 | int degm, degn; 271 | Vector ctrl[4][4]; 272 | double weight[4][4]; 273 | 274 | List trim; 275 | 276 | // For testing whether a point (u, v) on the surface lies inside the trim 277 | SBspUv *bsp; 278 | SEdgeList edges; 279 | 280 | // For caching our initial (u, v) when doing Newton iterations to project 281 | // a point into our surface. 282 | Point2d cached; 283 | 284 | static SSurface FromExtrusionOf(SBezier *spc, Vector t0, Vector t1); 285 | static SSurface FromRevolutionOf(SBezier *sb, Vector pt, Vector axis, 286 | double thetas, double thetaf); 287 | static SSurface FromPlane(Vector pt, Vector u, Vector v); 288 | static SSurface FromTransformationOf(SSurface *a, Vector t, Quaternion q, 289 | double scale, 290 | bool includingTrims); 291 | void ScaleSelfBy(double s); 292 | 293 | void EdgeNormalsWithinSurface(Point2d auv, Point2d buv, 294 | Vector *pt, Vector *enin, Vector *enout, 295 | Vector *surfn, 296 | uint32_t auxA, 297 | SShell *shell, SShell *sha, SShell *shb); 298 | void FindChainAvoiding(SEdgeList *src, SEdgeList *dest, SPointList *avoid); 299 | SSurface MakeCopyTrimAgainst(SShell *parent, SShell *a, SShell *b, 300 | SShell *into, SSurface::CombineAs type); 301 | void TrimFromEdgeList(SEdgeList *el, bool asUv); 302 | void IntersectAgainst(SSurface *b, SShell *agnstA, SShell *agnstB, 303 | SShell *into); 304 | void AddExactIntersectionCurve(SBezier *sb, SSurface *srfB, 305 | SShell *agnstA, SShell *agnstB, SShell *into); 306 | 307 | typedef struct { 308 | int tag; 309 | Point2d p; 310 | } Inter; 311 | void WeightControlPoints(); 312 | void UnWeightControlPoints(); 313 | void CopyRowOrCol(bool row, int this_ij, SSurface *src, int src_ij); 314 | void BlendRowOrCol(bool row, int this_ij, SSurface *a, int a_ij, 315 | SSurface *b, int b_ij); 316 | double DepartureFromCoplanar() const; 317 | void SplitInHalf(bool byU, SSurface *sa, SSurface *sb); 318 | void AllPointsIntersecting(Vector a, Vector b, 319 | List *l, 320 | bool asSegment, bool trimmed, bool inclTangent); 321 | void AllPointsIntersectingUntrimmed(Vector a, Vector b, 322 | int *cnt, int *level, 323 | List *l, bool asSegment, 324 | SSurface *sorig); 325 | 326 | void ClosestPointTo(Vector p, Point2d *puv, bool mustConverge=true); 327 | void ClosestPointTo(Vector p, double *u, double *v, bool mustConverge=true); 328 | bool ClosestPointNewton(Vector p, double *u, double *v, bool mustConverge=true) const; 329 | 330 | bool PointIntersectingLine(Vector p0, Vector p1, double *u, double *v) const; 331 | Vector ClosestPointOnThisAndSurface(SSurface *srf2, Vector p); 332 | void PointOnSurfaces(SSurface *s1, SSurface *s2, double *u, double *v); 333 | Vector PointAt(double u, double v) const; 334 | Vector PointAt(Point2d puv) const; 335 | void TangentsAt(double u, double v, Vector *tu, Vector *tv) const; 336 | Vector NormalAt(Point2d puv) const; 337 | Vector NormalAt(double u, double v) const; 338 | bool LineEntirelyOutsideBbox(Vector a, Vector b, bool asSegment) const; 339 | void GetAxisAlignedBounding(Vector *ptMax, Vector *ptMin) const; 340 | bool CoincidentWithPlane(Vector n, double d) const; 341 | bool CoincidentWith(SSurface *ss, bool sameNormal) const; 342 | bool IsExtrusion(SBezier *of, Vector *along) const; 343 | bool IsCylinder(Vector *axis, Vector *center, double *r, 344 | Vector *start, Vector *finish) const; 345 | 346 | void TriangulateInto(SShell *shell, SMesh *sm); 347 | 348 | // these are intended as bitmasks, even though there's just one now 349 | enum class MakeAs : uint32_t { 350 | UV = 0x01, 351 | XYZ = 0x00 352 | }; 353 | void MakeTrimEdgesInto(SEdgeList *sel, MakeAs flags, SCurve *sc, STrimBy *stb); 354 | void MakeEdgesInto(SShell *shell, SEdgeList *sel, MakeAs flags, 355 | SShell *useCurvesFrom=NULL); 356 | 357 | Vector ExactSurfaceTangentAt(Vector p, SSurface *srfA, SSurface *srfB, 358 | Vector dir); 359 | void MakeSectionEdgesInto(SShell *shell, SEdgeList *sel, SBezierList *sbl); 360 | void MakeClassifyingBsp(SShell *shell, SShell *useCurvesFrom); 361 | double ChordToleranceForEdge(Vector a, Vector b) const; 362 | void MakeTriangulationGridInto(List *l, double vs, double vf, 363 | bool swapped) const; 364 | Vector PointAtMaybeSwapped(double u, double v, bool swapped) const; 365 | 366 | void Reverse(); 367 | void Clear(); 368 | }; 369 | 370 | class SShell { 371 | public: 372 | IdList curve; 373 | IdList surface; 374 | 375 | bool booleanFailed; 376 | 377 | void MakeFromExtrusionOf(SBezierLoopSet *sbls, Vector t0, Vector t1, 378 | RgbaColor color); 379 | void MakeFromRevolutionOf(SBezierLoopSet *sbls, Vector pt, Vector axis, 380 | RgbaColor color, Group *group); 381 | 382 | void MakeFromUnionOf(SShell *a, SShell *b); 383 | void MakeFromDifferenceOf(SShell *a, SShell *b); 384 | void MakeFromBoolean(SShell *a, SShell *b, SSurface::CombineAs type); 385 | void CopyCurvesSplitAgainst(bool opA, SShell *agnst, SShell *into); 386 | void CopySurfacesTrimAgainst(SShell *sha, SShell *shb, SShell *into, SSurface::CombineAs type); 387 | void MakeIntersectionCurvesAgainst(SShell *against, SShell *into); 388 | void MakeClassifyingBsps(SShell *useCurvesFrom); 389 | void AllPointsIntersecting(Vector a, Vector b, List *il, 390 | bool asSegment, bool trimmed, bool inclTangent); 391 | void MakeCoincidentEdgesInto(SSurface *proto, bool sameNormal, 392 | SEdgeList *el, SShell *useCurvesFrom); 393 | void RewriteSurfaceHandlesForCurves(SShell *a, SShell *b); 394 | void CleanupAfterBoolean(); 395 | 396 | // Definitions when classifying regions of a surface; it is either inside, 397 | // outside, or coincident (with parallel or antiparallel normal) with a 398 | // shell. 399 | enum class Class : uint32_t { 400 | INSIDE = 100, 401 | OUTSIDE = 200, 402 | COINC_SAME = 300, 403 | COINC_OPP = 400 404 | }; 405 | static const double DOTP_TOL; 406 | Class ClassifyRegion(Vector edge_n, Vector inter_surf_n, 407 | Vector edge_surf_n) const; 408 | 409 | bool ClassifyEdge(Class *indir, Class *outdir, 410 | Vector ea, Vector eb, 411 | Vector p, Vector edge_n_in, 412 | Vector edge_n_out, Vector surf_n); 413 | 414 | void MakeFromCopyOf(SShell *a); 415 | void MakeFromTransformationOf(SShell *a, 416 | Vector trans, Quaternion q, double scale); 417 | void MakeFromAssemblyOf(SShell *a, SShell *b); 418 | void MergeCoincidentSurfaces(); 419 | 420 | void TriangulateInto(SMesh *sm); 421 | void MakeEdgesInto(SEdgeList *sel); 422 | void MakeSectionEdgesInto(Vector n, double d, SEdgeList *sel, SBezierList *sbl); 423 | bool IsEmpty() const; 424 | void RemapFaces(Group *g, int remap); 425 | void Clear(); 426 | }; 427 | 428 | #endif 429 | 430 | -------------------------------------------------------------------------------- /src/system.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Once we've written our constraint equations in the symbolic algebra system, 3 | // these routines linearize them, and solve by a modified Newton's method. 4 | // This also contains the routines to detect non-convergence or inconsistency, 5 | // and report diagnostics to the user. 6 | // 7 | // Copyright 2008-2013 Jonathan Westhues. 8 | //----------------------------------------------------------------------------- 9 | #include "solvespace.h" 10 | 11 | // This tolerance is used to determine whether two (linearized) constraints 12 | // are linearly dependent. If this is too small, then we will attempt to 13 | // solve truly inconsistent systems and fail. But if it's too large, then 14 | // we will give up on legitimate systems like a skinny right angle triangle by 15 | // its hypotenuse and long side. 16 | const double System::RANK_MAG_TOLERANCE = 1e-4; 17 | 18 | // The solver will converge all unknowns to within this tolerance. This must 19 | // always be much less than LENGTH_EPS, and in practice should be much less. 20 | const double System::CONVERGE_TOLERANCE = (LENGTH_EPS/(1e2)); 21 | 22 | bool System::WriteJacobian(int tag) { 23 | int a, i, j; 24 | 25 | j = 0; 26 | for(a = 0; a < param.n; a++) { 27 | if(j >= MAX_UNKNOWNS) return false; 28 | 29 | Param *p = &(param.elem[a]); 30 | if(p->tag != tag) continue; 31 | mat.param[j] = p->h; 32 | j++; 33 | } 34 | mat.n = j; 35 | 36 | i = 0; 37 | for(a = 0; a < eq.n; a++) { 38 | if(i >= MAX_UNKNOWNS) return false; 39 | 40 | Equation *e = &(eq.elem[a]); 41 | if(e->tag != tag) continue; 42 | 43 | mat.eq[i] = e->h; 44 | Expr *f = e->e->DeepCopyWithParamsAsPointers(¶m, &(SK.param)); 45 | f = f->FoldConstants(); 46 | 47 | // Hash table (61 bits) to accelerate generation of zero partials. 48 | uint64_t scoreboard = f->ParamsUsed(); 49 | for(j = 0; j < mat.n; j++) { 50 | Expr *pd; 51 | if(scoreboard & ((uint64_t)1 << (mat.param[j].v % 61)) && 52 | f->DependsOn(mat.param[j])) 53 | { 54 | pd = f->PartialWrt(mat.param[j]); 55 | pd = pd->FoldConstants(); 56 | pd = pd->DeepCopyWithParamsAsPointers(¶m, &(SK.param)); 57 | } else { 58 | pd = Expr::From(0.0); 59 | } 60 | mat.A.sym[i][j] = pd; 61 | } 62 | mat.B.sym[i] = f; 63 | i++; 64 | } 65 | mat.m = i; 66 | 67 | return true; 68 | } 69 | 70 | void System::EvalJacobian() { 71 | int i, j; 72 | for(i = 0; i < mat.m; i++) { 73 | for(j = 0; j < mat.n; j++) { 74 | mat.A.num[i][j] = (mat.A.sym[i][j])->Eval(); 75 | } 76 | } 77 | } 78 | 79 | bool System::IsDragged(hParam p) { 80 | hParam *pp; 81 | for(pp = dragged.First(); pp; pp = dragged.NextAfter(pp)) { 82 | if(p.v == pp->v) return true; 83 | } 84 | return false; 85 | } 86 | 87 | void System::SolveBySubstitution() { 88 | int i; 89 | for(i = 0; i < eq.n; i++) { 90 | Equation *teq = &(eq.elem[i]); 91 | Expr *tex = teq->e; 92 | 93 | if(tex->op == Expr::Op::MINUS && 94 | tex->a->op == Expr::Op::PARAM && 95 | tex->b->op == Expr::Op::PARAM) 96 | { 97 | hParam a = tex->a->parh; 98 | hParam b = tex->b->parh; 99 | if(!(param.FindByIdNoOops(a) && param.FindByIdNoOops(b))) { 100 | // Don't substitute unless they're both solver params; 101 | // otherwise it's an equation that can be solved immediately, 102 | // or an error to flag later. 103 | continue; 104 | } 105 | 106 | if(IsDragged(a)) { 107 | // A is being dragged, so A should stay, and B should go 108 | hParam t = a; 109 | a = b; 110 | b = t; 111 | } 112 | 113 | int j; 114 | for(j = 0; j < eq.n; j++) { 115 | Equation *req = &(eq.elem[j]); 116 | (req->e)->Substitute(a, b); // A becomes B, B unchanged 117 | } 118 | for(j = 0; j < param.n; j++) { 119 | Param *rp = &(param.elem[j]); 120 | if(rp->substd.v == a.v) { 121 | rp->substd = b; 122 | } 123 | } 124 | Param *ptr = param.FindById(a); 125 | ptr->tag = VAR_SUBSTITUTED; 126 | ptr->substd = b; 127 | 128 | teq->tag = EQ_SUBSTITUTED; 129 | } 130 | } 131 | } 132 | 133 | //----------------------------------------------------------------------------- 134 | // Calculate the rank of the Jacobian matrix, by Gram-Schimdt orthogonalization 135 | // in place. A row (~equation) is considered to be all zeros if its magnitude 136 | // is less than the tolerance RANK_MAG_TOLERANCE. 137 | //----------------------------------------------------------------------------- 138 | int System::CalculateRank() { 139 | // Actually work with magnitudes squared, not the magnitudes 140 | double rowMag[MAX_UNKNOWNS] = {}; 141 | double tol = RANK_MAG_TOLERANCE*RANK_MAG_TOLERANCE; 142 | 143 | int i, iprev, j; 144 | int rank = 0; 145 | 146 | for(i = 0; i < mat.m; i++) { 147 | // Subtract off this row's component in the direction of any 148 | // previous rows 149 | for(iprev = 0; iprev < i; iprev++) { 150 | if(rowMag[iprev] <= tol) continue; // ignore zero rows 151 | 152 | double dot = 0; 153 | for(j = 0; j < mat.n; j++) { 154 | dot += (mat.A.num[iprev][j]) * (mat.A.num[i][j]); 155 | } 156 | for(j = 0; j < mat.n; j++) { 157 | mat.A.num[i][j] -= (dot/rowMag[iprev])*mat.A.num[iprev][j]; 158 | } 159 | } 160 | // Our row is now normal to all previous rows; calculate the 161 | // magnitude of what's left 162 | double mag = 0; 163 | for(j = 0; j < mat.n; j++) { 164 | mag += (mat.A.num[i][j]) * (mat.A.num[i][j]); 165 | } 166 | if(mag > tol) { 167 | rank++; 168 | } 169 | rowMag[i] = mag; 170 | } 171 | 172 | return rank; 173 | } 174 | 175 | bool System::TestRank() { 176 | EvalJacobian(); 177 | return CalculateRank() == mat.m; 178 | } 179 | 180 | bool System::SolveLinearSystem(double X[], double A[][MAX_UNKNOWNS], 181 | double B[], int n) 182 | { 183 | // Gaussian elimination, with partial pivoting. It's an error if the 184 | // matrix is singular, because that means two constraints are 185 | // equivalent. 186 | int i, j, ip, jp, imax = 0; 187 | double max, temp; 188 | 189 | for(i = 0; i < n; i++) { 190 | // We are trying eliminate the term in column i, for rows i+1 and 191 | // greater. First, find a pivot (between rows i and N-1). 192 | max = 0; 193 | for(ip = i; ip < n; ip++) { 194 | if(ffabs(A[ip][i]) > max) { 195 | imax = ip; 196 | max = ffabs(A[ip][i]); 197 | } 198 | } 199 | // Don't give up on a singular matrix unless it's really bad; the 200 | // assumption code is responsible for identifying that condition, 201 | // so we're not responsible for reporting that error. 202 | if(ffabs(max) < 1e-20) continue; 203 | 204 | // Swap row imax with row i 205 | for(jp = 0; jp < n; jp++) { 206 | swap(A[i][jp], A[imax][jp]); 207 | } 208 | swap(B[i], B[imax]); 209 | 210 | // For rows i+1 and greater, eliminate the term in column i. 211 | for(ip = i+1; ip < n; ip++) { 212 | temp = A[ip][i]/A[i][i]; 213 | 214 | for(jp = i; jp < n; jp++) { 215 | A[ip][jp] -= temp*(A[i][jp]); 216 | } 217 | B[ip] -= temp*B[i]; 218 | } 219 | } 220 | 221 | // We've put the matrix in upper triangular form, so at this point we 222 | // can solve by back-substitution. 223 | for(i = n - 1; i >= 0; i--) { 224 | if(ffabs(A[i][i]) < 1e-20) continue; 225 | 226 | temp = B[i]; 227 | for(j = n - 1; j > i; j--) { 228 | temp -= X[j]*A[i][j]; 229 | } 230 | X[i] = temp / A[i][i]; 231 | } 232 | 233 | return true; 234 | } 235 | 236 | bool System::SolveLeastSquares() { 237 | int r, c, i; 238 | 239 | // Scale the columns; this scale weights the parameters for the least 240 | // squares solve, so that we can encourage the solver to make bigger 241 | // changes in some parameters, and smaller in others. 242 | for(c = 0; c < mat.n; c++) { 243 | if(IsDragged(mat.param[c])) { 244 | // It's least squares, so this parameter doesn't need to be all 245 | // that big to get a large effect. 246 | mat.scale[c] = 1/20.0; 247 | } else { 248 | mat.scale[c] = 1; 249 | } 250 | for(r = 0; r < mat.m; r++) { 251 | mat.A.num[r][c] *= mat.scale[c]; 252 | } 253 | } 254 | 255 | // Write A*A' 256 | for(r = 0; r < mat.m; r++) { 257 | for(c = 0; c < mat.m; c++) { // yes, AAt is square 258 | double sum = 0; 259 | for(i = 0; i < mat.n; i++) { 260 | sum += mat.A.num[r][i]*mat.A.num[c][i]; 261 | } 262 | mat.AAt[r][c] = sum; 263 | } 264 | } 265 | 266 | if(!SolveLinearSystem(mat.Z, mat.AAt, mat.B.num, mat.m)) return false; 267 | 268 | // And multiply that by A' to get our solution. 269 | for(c = 0; c < mat.n; c++) { 270 | double sum = 0; 271 | for(i = 0; i < mat.m; i++) { 272 | sum += mat.A.num[i][c]*mat.Z[i]; 273 | } 274 | mat.X[c] = sum * mat.scale[c]; 275 | } 276 | return true; 277 | } 278 | 279 | bool System::NewtonSolve(int tag) { 280 | 281 | int iter = 0; 282 | bool converged = false; 283 | int i; 284 | 285 | // Evaluate the functions at our operating point. 286 | for(i = 0; i < mat.m; i++) { 287 | mat.B.num[i] = (mat.B.sym[i])->Eval(); 288 | } 289 | do { 290 | // And evaluate the Jacobian at our initial operating point. 291 | EvalJacobian(); 292 | 293 | if(!SolveLeastSquares()) break; 294 | 295 | // Take the Newton step; 296 | // J(x_n) (x_{n+1} - x_n) = 0 - F(x_n) 297 | for(i = 0; i < mat.n; i++) { 298 | Param *p = param.FindById(mat.param[i]); 299 | p->val -= mat.X[i]; 300 | if(isnan(p->val)) { 301 | // Very bad, and clearly not convergent 302 | return false; 303 | } 304 | } 305 | 306 | // Re-evalute the functions, since the params have just changed. 307 | for(i = 0; i < mat.m; i++) { 308 | mat.B.num[i] = (mat.B.sym[i])->Eval(); 309 | } 310 | // Check for convergence 311 | converged = true; 312 | for(i = 0; i < mat.m; i++) { 313 | if(isnan(mat.B.num[i])) { 314 | return false; 315 | } 316 | if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE) { 317 | converged = false; 318 | break; 319 | } 320 | } 321 | } while(iter++ < 50 && !converged); 322 | 323 | return converged; 324 | } 325 | 326 | void System::WriteEquationsExceptFor(hConstraint hc, Group *g) { 327 | int i; 328 | // Generate all the equations from constraints in this group 329 | for(i = 0; i < SK.constraint.n; i++) { 330 | ConstraintBase *c = &(SK.constraint.elem[i]); 331 | if(c->group.v != g->h.v) continue; 332 | if(c->h.v == hc.v) continue; 333 | 334 | if(c->HasLabel() && c->type != Constraint::Type::COMMENT && 335 | g->allDimsReference) 336 | { 337 | // When all dimensions are reference, we adjust them to display 338 | // the correct value, and then don't generate any equations. 339 | c->ModifyToSatisfy(); 340 | continue; 341 | } 342 | if(g->relaxConstraints && c->type != Constraint::Type::POINTS_COINCIDENT) { 343 | // When the constraints are relaxed, we keep only the point- 344 | // coincident constraints, and the constraints generated by 345 | // the entities and groups. 346 | continue; 347 | } 348 | 349 | c->GenerateEquations(&eq); 350 | } 351 | // And the equations from entities 352 | for(i = 0; i < SK.entity.n; i++) { 353 | EntityBase *e = &(SK.entity.elem[i]); 354 | if(e->group.v != g->h.v) continue; 355 | 356 | e->GenerateEquations(&eq); 357 | } 358 | // And from the groups themselves 359 | g->GenerateEquations(&eq); 360 | } 361 | 362 | void System::FindWhichToRemoveToFixJacobian(Group *g, List *bad, bool forceDofCheck) { 363 | int a, i; 364 | 365 | for(a = 0; a < 2; a++) { 366 | for(i = 0; i < SK.constraint.n; i++) { 367 | ConstraintBase *c = &(SK.constraint.elem[i]); 368 | if(c->group.v != g->h.v) continue; 369 | if((c->type == Constraint::Type::POINTS_COINCIDENT && a == 0) || 370 | (c->type != Constraint::Type::POINTS_COINCIDENT && a == 1)) 371 | { 372 | // Do the constraints in two passes: first everything but 373 | // the point-coincident constraints, then only those 374 | // constraints (so they appear last in the list). 375 | continue; 376 | } 377 | 378 | param.ClearTags(); 379 | eq.Clear(); 380 | WriteEquationsExceptFor(c->h, g); 381 | eq.ClearTags(); 382 | 383 | // It's a major speedup to solve the easy ones by substitution here, 384 | // and that doesn't break anything. 385 | if(!forceDofCheck) { 386 | SolveBySubstitution(); 387 | } 388 | 389 | WriteJacobian(0); 390 | EvalJacobian(); 391 | 392 | int rank = CalculateRank(); 393 | if(rank == mat.m) { 394 | // We fixed it by removing this constraint 395 | bad->Add(&(c->h)); 396 | } 397 | } 398 | } 399 | } 400 | 401 | SolveResult System::Solve(Group *g, int *dof, List *bad, 402 | bool andFindBad, bool andFindFree, bool forceDofCheck) 403 | { 404 | WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g); 405 | 406 | int i; 407 | bool rankOk; 408 | 409 | /* 410 | dbp("%d equations", eq.n); 411 | for(i = 0; i < eq.n; i++) { 412 | dbp(" %.3f = %s = 0", eq.elem[i].e->Eval(), eq.elem[i].e->Print()); 413 | } 414 | dbp("%d parameters", param.n); 415 | for(i = 0; i < param.n; i++) { 416 | dbp(" param %08x at %.3f", param.elem[i].h.v, param.elem[i].val); 417 | } */ 418 | 419 | // All params and equations are assigned to group zero. 420 | param.ClearTags(); 421 | eq.ClearTags(); 422 | 423 | if(!forceDofCheck) { 424 | SolveBySubstitution(); 425 | } 426 | 427 | // Before solving the big system, see if we can find any equations that 428 | // are soluble alone. This can be a huge speedup. We don't know whether 429 | // the system is consistent yet, but if it isn't then we'll catch that 430 | // later. 431 | int alone = 1; 432 | for(i = 0; i < eq.n; i++) { 433 | Equation *e = &(eq.elem[i]); 434 | if(e->tag != 0) continue; 435 | 436 | hParam hp = e->e->ReferencedParams(¶m); 437 | if(hp.v == Expr::NO_PARAMS.v) continue; 438 | if(hp.v == Expr::MULTIPLE_PARAMS.v) continue; 439 | 440 | Param *p = param.FindById(hp); 441 | if(p->tag != 0) continue; // let rank test catch inconsistency 442 | 443 | e->tag = alone; 444 | p->tag = alone; 445 | WriteJacobian(alone); 446 | if(!NewtonSolve(alone)) { 447 | // We don't do the rank test, so let's arbitrarily return 448 | // the DIDNT_CONVERGE result here. 449 | rankOk = true; 450 | // Failed to converge, bail out early 451 | goto didnt_converge; 452 | } 453 | alone++; 454 | } 455 | 456 | // Now write the Jacobian for what's left, and do a rank test; that 457 | // tells us if the system is inconsistently constrained. 458 | if(!WriteJacobian(0)) { 459 | return SolveResult::TOO_MANY_UNKNOWNS; 460 | } 461 | 462 | rankOk = TestRank(); 463 | 464 | // And do the leftovers as one big system 465 | if(!NewtonSolve(0)) { 466 | goto didnt_converge; 467 | } 468 | 469 | rankOk = TestRank(); 470 | if(!rankOk) { 471 | if(!g->allowRedundant) { 472 | if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, forceDofCheck); 473 | } 474 | } else { 475 | // This is not the full Jacobian, but any substitutions or single-eq 476 | // solves removed one equation and one unknown, therefore no effect 477 | // on the number of DOF. 478 | if(dof) *dof = CalculateDof(); 479 | MarkParamsFree(andFindFree); 480 | } 481 | // System solved correctly, so write the new values back in to the 482 | // main parameter table. 483 | for(i = 0; i < param.n; i++) { 484 | Param *p = &(param.elem[i]); 485 | double val; 486 | if(p->tag == VAR_SUBSTITUTED) { 487 | val = param.FindById(p->substd)->val; 488 | } else { 489 | val = p->val; 490 | } 491 | Param *pp = SK.GetParam(p->h); 492 | pp->val = val; 493 | pp->known = true; 494 | pp->free = p->free; 495 | } 496 | return rankOk ? SolveResult::OKAY : SolveResult::REDUNDANT_OKAY; 497 | 498 | didnt_converge: 499 | SK.constraint.ClearTags(); 500 | for(i = 0; i < eq.n; i++) { 501 | if(ffabs(mat.B.num[i]) > CONVERGE_TOLERANCE || isnan(mat.B.num[i])) { 502 | // This constraint is unsatisfied. 503 | if(!mat.eq[i].isFromConstraint()) continue; 504 | 505 | hConstraint hc = mat.eq[i].constraint(); 506 | ConstraintBase *c = SK.constraint.FindByIdNoOops(hc); 507 | if(!c) continue; 508 | // Don't double-show constraints that generated multiple 509 | // unsatisfied equations 510 | if(!c->tag) { 511 | bad->Add(&(c->h)); 512 | c->tag = 1; 513 | } 514 | } 515 | } 516 | 517 | return rankOk ? SolveResult::DIDNT_CONVERGE : SolveResult::REDUNDANT_DIDNT_CONVERGE; 518 | } 519 | 520 | SolveResult System::SolveRank(Group *g, int *dof, List *bad, 521 | bool andFindBad, bool andFindFree, bool forceDofCheck) 522 | { 523 | WriteEquationsExceptFor(Constraint::NO_CONSTRAINT, g); 524 | 525 | // All params and equations are assigned to group zero. 526 | param.ClearTags(); 527 | eq.ClearTags(); 528 | 529 | if(!forceDofCheck) { 530 | SolveBySubstitution(); 531 | } 532 | 533 | // Now write the Jacobian, and do a rank test; that 534 | // tells us if the system is inconsistently constrained. 535 | if(!WriteJacobian(0)) { 536 | return SolveResult::TOO_MANY_UNKNOWNS; 537 | } 538 | 539 | bool rankOk = TestRank(); 540 | if(!rankOk) { 541 | if(!g->allowRedundant) { 542 | if(andFindBad) FindWhichToRemoveToFixJacobian(g, bad, forceDofCheck); 543 | } 544 | } else { 545 | // This is not the full Jacobian, but any substitutions or single-eq 546 | // solves removed one equation and one unknown, therefore no effect 547 | // on the number of DOF. 548 | if(dof) *dof = CalculateDof(); 549 | MarkParamsFree(andFindFree); 550 | } 551 | return rankOk ? SolveResult::OKAY : SolveResult::REDUNDANT_OKAY; 552 | } 553 | 554 | void System::Clear() { 555 | entity.Clear(); 556 | param.Clear(); 557 | eq.Clear(); 558 | dragged.Clear(); 559 | } 560 | 561 | void System::MarkParamsFree(bool find) { 562 | // If requested, find all the free (unbound) variables. This might be 563 | // more than the number of degrees of freedom. Don't always do this, 564 | // because the display would get annoying and it's slow. 565 | for(int i = 0; i < param.n; i++) { 566 | Param *p = &(param.elem[i]); 567 | p->free = false; 568 | 569 | if(find) { 570 | if(p->tag == 0) { 571 | p->tag = VAR_DOF_TEST; 572 | WriteJacobian(0); 573 | EvalJacobian(); 574 | int rank = CalculateRank(); 575 | if(rank == mat.m) { 576 | p->free = true; 577 | } 578 | p->tag = 0; 579 | } 580 | } 581 | } 582 | } 583 | 584 | int System::CalculateDof() { 585 | return mat.n - mat.m; 586 | } 587 | 588 | -------------------------------------------------------------------------------- /src/ttf.h: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Routines to read a TrueType font as vector outlines, and generate them 3 | // as entities, since they're always representable as either lines or 4 | // quadratic Bezier curves. 5 | // 6 | // Copyright 2016 whitequark, Peter Barfuss. 7 | //----------------------------------------------------------------------------- 8 | 9 | #ifndef __TTF_H 10 | #define __TTF_H 11 | 12 | class TtfFont { 13 | public: 14 | Platform::Path fontFile; 15 | std::string name; 16 | FT_FaceRec_ *fontFace; 17 | double capHeight; 18 | 19 | std::string FontFileBaseName() const; 20 | bool LoadFromFile(FT_LibraryRec_ *fontLibrary, bool nameOnly = true); 21 | 22 | void PlotString(const std::string &str, 23 | SBezierList *sbl, Vector origin, Vector u, Vector v); 24 | double AspectRatio(const std::string &str); 25 | }; 26 | 27 | class TtfFontList { 28 | public: 29 | FT_LibraryRec_ *fontLibrary; 30 | bool loaded; 31 | List l; 32 | 33 | TtfFontList(); 34 | ~TtfFontList(); 35 | 36 | void LoadAll(); 37 | TtfFont *LoadFont(const std::string &font); 38 | 39 | void PlotString(const std::string &font, const std::string &str, 40 | SBezierList *sbl, Vector origin, Vector u, Vector v); 41 | double AspectRatio(const std::string &font, const std::string &str); 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /test_slvs.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """This module will test the functions of Python-Solvespace.""" 4 | 5 | __author__ = "Yuan Chang" 6 | __copyright__ = "Copyright (C) 2016-2019" 7 | __license__ = "AGPL" 8 | __email__ = "pyslvs@gmail.com" 9 | 10 | import unittest 11 | from unittest import TestCase 12 | from math import radians 13 | from slvs import ResultFlag, SolverSystem, make_quaternion 14 | 15 | 16 | class CoreTest(TestCase): 17 | 18 | def test_crank_rocker(self): 19 | """Crank rocker example.""" 20 | sys = SolverSystem() 21 | wp = sys.create_2d_base() 22 | p0 = sys.add_point_2d(0, 0, wp) 23 | sys.dragged(p0, wp) 24 | p1 = sys.add_point_2d(90, 0, wp) 25 | sys.dragged(p1, wp) 26 | line0 = sys.add_line_2d(p0, p1, wp) 27 | p2 = sys.add_point_2d(20, 20, wp) 28 | p3 = sys.add_point_2d(0, 10, wp) 29 | p4 = sys.add_point_2d(30, 20, wp) 30 | sys.distance(p2, p3, 40, wp) 31 | sys.distance(p2, p4, 40, wp) 32 | sys.distance(p3, p4, 70, wp) 33 | 34 | sys.distance(p0, p3, 35, wp) 35 | sys.distance(p1, p4, 70, wp) 36 | line1 = sys.add_line_2d(p0, p3, wp) 37 | sys.angle(line0, line1, 45, wp) 38 | 39 | result_flag = sys.solve() 40 | self.assertEqual(result_flag, ResultFlag.OKAY) 41 | x, y = sys.params(p2.params) 42 | self.assertAlmostEqual(39.54852, x, 4) 43 | self.assertAlmostEqual(61.91009, y, 4) 44 | 45 | def test_involute(self): 46 | """Involute example.""" 47 | r = 10 48 | angle = 45 49 | 50 | sys = SolverSystem() 51 | wp = sys.create_2d_base() 52 | p0 = sys.add_point_2d(0, 0, wp) 53 | sys.dragged(p0, wp) 54 | 55 | p1 = sys.add_point_2d(0, 10, wp) 56 | sys.distance(p0, p1, r, wp) 57 | line0 = sys.add_line_2d(p0, p1, wp) 58 | 59 | p2 = sys.add_point_2d(10, 10, wp) 60 | line1 = sys.add_line_2d(p1, p2, wp) 61 | sys.distance(p1, p2, r * radians(angle), wp) 62 | sys.perpendicular(line0, line1, wp, False) 63 | 64 | p3 = sys.add_point_2d(10, 0, wp) 65 | sys.dragged(p3, wp) 66 | line_base = sys.add_line_2d(p0, p3, wp) 67 | sys.angle(line0, line_base, angle, wp) 68 | 69 | result_flag = sys.solve() 70 | self.assertEqual(result_flag, ResultFlag.OKAY) 71 | x, y = sys.params(p2.params) 72 | self.assertAlmostEqual(12.62467, x, 4) 73 | self.assertAlmostEqual(1.51746, y, 4) 74 | 75 | def test_jansen_linkage(self): 76 | """Jansen's linkage example.""" 77 | sys = SolverSystem() 78 | wp = sys.create_2d_base() 79 | p0 = sys.add_point_2d(0, 0, wp) 80 | sys.dragged(p0, wp) 81 | 82 | p1 = sys.add_point_2d(0, 20, wp) 83 | sys.distance(p0, p1, 15, wp) 84 | line0 = sys.add_line_2d(p0, p1, wp) 85 | 86 | p2 = sys.add_point_2d(-38, -7.8, wp) 87 | sys.dragged(p2, wp) 88 | p3 = sys.add_point_2d(-50, 30, wp) 89 | p4 = sys.add_point_2d(-70, -15, wp) 90 | sys.distance(p2, p3, 41.5, wp) 91 | sys.distance(p3, p4, 55.8, wp) 92 | sys.distance(p2, p4, 40.1, wp) 93 | 94 | p5 = sys.add_point_2d(-50, -50, wp) 95 | p6 = sys.add_point_2d(-10, -90, wp) 96 | p7 = sys.add_point_2d(-20, -40, wp) 97 | sys.distance(p5, p6, 65.7, wp) 98 | sys.distance(p6, p7, 49.0, wp) 99 | sys.distance(p5, p7, 36.7, wp) 100 | 101 | sys.distance(p1, p3, 50, wp) 102 | sys.distance(p1, p7, 61.9, wp) 103 | 104 | p8 = sys.add_point_2d(20, 0, wp) 105 | line_base = sys.add_line_2d(p0, p8, wp) 106 | sys.angle(line0, line_base, 45, wp) 107 | 108 | result_flag = sys.solve() 109 | self.assertEqual(result_flag, ResultFlag.OKAY) 110 | x, y = sys.params(p2.params) 111 | self.assertAlmostEqual(-38, x, 4) 112 | self.assertAlmostEqual(-7.8, y, 4) 113 | 114 | def test_nut_cracker(self): 115 | """Nut cracker example.""" 116 | h0 = 0.5 117 | b0 = 0.75 118 | r0 = 0.25 119 | n1 = 1.5 120 | n2 = 2.3 121 | l0 = 3.25 122 | 123 | sys = SolverSystem() 124 | wp = sys.create_2d_base() 125 | p0 = sys.add_point_2d(0, 0, wp) 126 | sys.dragged(p0, wp) 127 | 128 | p1 = sys.add_point_2d(2, 2, wp) 129 | p2 = sys.add_point_2d(2, 0, wp) 130 | line0 = sys.add_line_2d(p0, p2, wp) 131 | sys.horizontal(line0, wp) 132 | 133 | line1 = sys.add_line_2d(p1, p2, wp) 134 | p3 = sys.add_point_2d(b0 / 2, h0, wp) 135 | sys.dragged(p3, wp) 136 | sys.distance(p3, line1, r0, wp) 137 | sys.distance(p0, p1, n1, wp) 138 | sys.distance(p1, p2, n2, wp) 139 | 140 | result_flag = sys.solve() 141 | self.assertEqual(result_flag, ResultFlag.OKAY) 142 | x, _ = sys.params(p2.params) 143 | ans_min = x - b0 / 2 144 | ans_max = l0 - r0 - b0 / 2 145 | self.assertAlmostEqual(1.01576, ans_min, 4) 146 | self.assertAlmostEqual(2.625, ans_max, 4) 147 | 148 | def test_pydemo(self): 149 | """ 150 | Some sample code for slvs.dll. We draw some geometric entities, provide 151 | initial guesses for their positions, and then constrain them. The solver 152 | calculates their new positions, in order to satisfy the constraints. 153 | 154 | Copyright 2008-2013 Jonathan Westhues. 155 | Copyright 2016-2017 Yuan Chang [pyslvs@gmail.com] Python-Solvespace bundled. 156 | 157 | An example of a constraint in 2d. In our first group, we create a workplane 158 | along the reference frame's xy plane. In a second group, we create some 159 | entities in that group and dimension them. 160 | """ 161 | sys = SolverSystem() 162 | sys.set_group(1) 163 | 164 | # First, we create our workplane. Its origin corresponds to the origin 165 | # of our base frame (x y z) = (0 0 0) 166 | p101 = sys.add_point_3d(0, 0, 0) 167 | # and it is parallel to the xy plane, so it has basis vectors (1 0 0) 168 | # and (0 1 0). 169 | qw, qx, qy, qz = make_quaternion(1, 0, 0, 0, 1, 0) 170 | n102 = sys.add_normal_3d(qw, qx, qy, qz) 171 | wp200 = sys.add_work_plane(p101, n102) 172 | 173 | # Now create a second group. We'll solve group 2, while leaving group 1 174 | # constant; so the workplane that we've created will be locked down, 175 | # and the solver can't move it. 176 | sys.set_group(2) 177 | p301 = sys.add_point_2d(10, 20, wp200) 178 | p302 = sys.add_point_2d(20, 10, wp200) 179 | 180 | # And we create a line segment with those endpoints. 181 | l400 = sys.add_line_2d(p301, p302, wp200) 182 | 183 | # Now three more points. 184 | p303 = sys.add_point_2d(100, 120, wp200) 185 | p304 = sys.add_point_2d(120, 110, wp200) 186 | p305 = sys.add_point_2d(115, 115, wp200) 187 | 188 | # And arc, centered at point 303, starting at point 304, ending at 189 | # point 305. 190 | a401 = sys.add_arc(n102, p303, p304, p305, wp200) 191 | 192 | # Now one more point, and a distance 193 | p306 = sys.add_point_2d(200, 200, wp200) 194 | d307 = sys.add_distance(30, wp200) 195 | 196 | # And a complete circle, centered at point 306 with radius equal to 197 | # distance 307. The normal is 102, the same as our workplane. 198 | c402 = sys.add_circle(n102, p306, d307, wp200) 199 | 200 | # The length of our line segment is 30.0 units. 201 | sys.distance(p301, p302, 30, wp200) 202 | 203 | # And the distance from our line segment to the origin is 10.0 units. 204 | sys.distance(p101, l400, 10, wp200) 205 | 206 | # And the line segment is vertical. 207 | sys.vertical(l400, wp200) 208 | 209 | # And the distance from one endpoint to the origin is 15.0 units. 210 | sys.distance(p301, p101, 15, wp200) 211 | 212 | # The arc and the circle have equal radius. 213 | sys.equal(a401, c402, wp200) 214 | 215 | # The arc has radius 17.0 units. 216 | sys.diameter(a401, 17 * 2, wp200) 217 | 218 | # If the solver fails, then ask it to report which constraints caused 219 | # the problem. 220 | 221 | # And solve. 222 | result_flag = sys.solve() 223 | self.assertEqual(result_flag, ResultFlag.OKAY) 224 | x, y = sys.params(p301.params) 225 | self.assertAlmostEqual(10, x, 4) 226 | self.assertAlmostEqual(11.18030, y, 4) 227 | x, y = sys.params(p302.params) 228 | self.assertAlmostEqual(10, x, 4) 229 | self.assertAlmostEqual(-18.81966, y, 4) 230 | x, y = sys.params(p303.params) 231 | self.assertAlmostEqual(101.11418, x, 4) 232 | self.assertAlmostEqual(119.04153, y, 4) 233 | x, y = sys.params(p304.params) 234 | self.assertAlmostEqual(116.47661, x, 4) 235 | self.assertAlmostEqual(111.76171, y, 4) 236 | x, y = sys.params(p305.params) 237 | self.assertAlmostEqual(117.40922, x, 4) 238 | self.assertAlmostEqual(114.19676, y, 4) 239 | x, y = sys.params(p306.params) 240 | self.assertAlmostEqual(200, x, 4) 241 | self.assertAlmostEqual(200, y, 4) 242 | x, = sys.params(d307.params) 243 | self.assertAlmostEqual(17, x, 4) 244 | self.assertEqual(6, sys.dof()) 245 | 246 | 247 | if __name__ == '__main__': 248 | unittest.main() 249 | --------------------------------------------------------------------------------