Python-Solvespace 更新
-
切換群組功能
-
PyDemo.py
Python-Solvespace 更新
最近突然臨時起意,想將 Python-Solvespace 更新後 pull request 給官方,是否有 Python 介面的需求。
有以下特點:
-
對 Python 友善的介面,且比 C 語言只能使用 slvs.h 方便。
-
包含最新的 kernel,編譯無須 CMake。
不過使用過程中正克服一些障礙,讓功能與 C 語言版本一樣齊全,可以完勝 slvs.h 的 API。
切換群組功能
首先要翻製 CDemo 的範例內容,以讓相關人員可以更快瞭解操作。
不過 CDemo 中分開了群組,因此自由度只會計算該群組的成員。
原作者打算讓群組編號 Slvs_hGroup 和 Python int 對應 (mapping),不過這個功能竟然沒做!
直接使用 SWIG 對應可能導致類型混亂,因此我利用一個簡單的轉換式達成:
//"src/slvs_python.hpp"
Slvs_hGroup groupNum(int input) { return (Slvs_hGroup) input; }
SWIG 端口也很簡單,使用 inline 區塊可以不用寫兩行:
//"src/slvs.i"
%inline %{
extern Slvs_hGroup groupNum(int input);
%}
不過隨後 Python 發出警告:
swig/python detected a memory leak of type 'Slvs_hParam *', no destructor found.
無法找到解構 (destructor) 函式,即 __del__()或 C 語言的 ~some_class() 刪除函式。
之前 Slvs_hParam 等類別是在程式庫中運作,並沒有「顯現」出來讓 Python 操作,首次進入 Python 的記憶體管理範圍中,卻發生沒辦法清除的情況。
這樣會導致程式中止後佔用記憶體,只能靠作業系統清除。
找了一下 Solvespace 的原始碼,這些類別是定義自 uint32_t 整數類別:
//"include/slvs.h" typedef uint32_t Slvs_hParam; typedef uint32_t Slvs_hEntity; typedef uint32_t Slvs_hConstraint; typedef uint32_t Slvs_hGroup;
而 uint32_t 整數類別是包含自 stdint.h。
SWIG 有針對 C 語言的主要類別做轉換支援,因此在介面檔開頭加上:
//"src/slvs.i" //Let Python enable to delete Slvs_hParam, Slvs_hEntity, ... types. %include "stdint.i"
這樣就解決問題了!
函式 groupNum 的使用方法很簡單,即利用一個整數來產生群組,切換群組後即可新增所需項目,最大的特點是可以分開解題(包含自由度)。
sys = System() #切換至群組 1 g1 = groupNum(1) sys.default_group = g1 #切換至群組 2 g2 = groupNum(2) sys.default_group = g2
注意 groupNum 函式與一般 Slvs_hGroup 類別是回傳值,因此相同的輸入值會得到相同的結果,不用像其他實體或約束特意保留指標。
PyDemo.py
完全仿照 CDemo 製成的小腳本,主要展現 Python-Solvespace 應用程式界面精簡的特性。
之前原作者有做 Python 2 版的單元測試腳本,不過還掛載沒用到的套件,因此今天花了一點時間重寫了一下。
原先的註解都搬了過來。
# -*- coding: utf-8 -*-
## Some sample code for slvs.dll. We draw some geometric entities, provide
## initial guesses for their positions, and then constrain them. The solver
## calculates their new positions, in order to satisfy the constraints.
##
## Copyright 2008-2013 Jonathan Westhues.
## Copyright 2016-2017 Yuan Chang [pyslvs@gmail.com] Python-Solvespace bundled.
from slvs import *
sys = System()
'''
An example of a constraint in 3d. We create a single group, with some
entities and constraints.
'''
def Example3d():
#A point, initially at (x y z) = (10 10 10)
p0 = sys.add_param(10)
p1 = sys.add_param(10)
p2 = sys.add_param(10)
Point101 = Point3d(p0, p1, p2)
#and a second point at (20 20 20)
p3 = sys.add_param(20)
p4 = sys.add_param(20)
p5 = sys.add_param(20)
Point102 = Point3d(p3, p4, p5)
#and a line segment connecting them.
LineSegment3d(Point101, Point102)
#The distance between the points should be 30.0 units.
Constraint.distance(30., Point101, Point102)
#Let's tell the solver to keep the second point as close to constant
#as possible, instead moving the first point.
Constraint.dragged(Point102)
#Now that we have written our system, we solve.
result = sys.solve()
if result == SLVS_RESULT_OKAY:
print(
"okay; now at ({:.3f} {:.3f} {:.3f})\n".format(sys.get_param(0).val, sys.get_param(1).val, sys.get_param(2).val)+
" ({:.3f} {:.3f} {:.3f})\n".format(sys.get_param(3).val, sys.get_param(4).val, sys.get_param(5).val)
)
print("{} DOF".format(sys.dof))
else:
print("solve failed")
'''
An example of a constraint in 2d. In our first group, we create a workplane
along the reference frame's xy plane. In a second group, we create some
entities in that group and dimension them.
'''
def Example2d():
g1 = groupNum(1)
sys.default_group = g1
#First, we create our workplane. Its origin corresponds to the origin
#of our base frame (x y z) = (0 0 0)
p0 = sys.add_param(0)
p1 = sys.add_param(0)
p2 = sys.add_param(0)
Point101 = Point3d(p0, p1, p2)
#and it is parallel to the xy plane, so it has basis vectors (1 0 0)
#and (0 1 0).
qw, qx, qy, qz = Slvs_MakeQuaternion(*[1, 0, 0], *[0, 1, 0])
p3 = sys.add_param(qw)
p4 = sys.add_param(qx)
p5 = sys.add_param(qy)
p6 = sys.add_param(qz)
Normal102 = Normal3d(p3, p4, p5, p6)
Workplane200 = Workplane(Point101, Normal102)
#Now create a second group. We'll solve group 2, while leaving group 1
#constant; so the workplane that we've created will be locked down,
#and the solver can't move it.
g2 = groupNum(2)
sys.default_group = g2
#These points are represented by their coordinates (u v) within the
#workplane, so they need only two parameters each.
p7 = sys.add_param(10)
p8 = sys.add_param(20)
Point301 = Point2d(Workplane200, p7, p8)
p9 = sys.add_param(20)
p10 = sys.add_param(10)
Point302 = Point2d(Workplane200, p9, p10)
#And we create a line segment with those endpoints.
Line400 = LineSegment2d(Workplane200, Point301, Point302)
#Now three more points.
p11 = sys.add_param(100)
p12 = sys.add_param(120)
Point303 = Point2d(Workplane200, p11, p12)
p13 = sys.add_param(120)
p14 = sys.add_param(110)
Point304 = Point2d(Workplane200, p13, p14)
p15 = sys.add_param(115)
p16 = sys.add_param(115)
Point305 = Point2d(Workplane200, p15, p16)
#And arc, centered at point 303, starting at point 304, ending at
#point 305.
Arc401 = ArcOfCircle(Workplane200, Normal102, Point303, Point304, Point305)
#Now one more point, and a distance
p17 = sys.add_param(200)
p18 = sys.add_param(200)
Point306 = Point2d(Workplane200, p17, p18)
p19 = sys.add_param(30)
Distance0 = Distance(Workplane200, p19)
#And a complete circle, centered at point 306 with radius equal to
#distance 307. The normal is 102, the same as our workplane.
Circle402 = Circle(Workplane200, Normal102, Point306, Distance0)
#The length of our line segment is 30.0 units.
Constraint.distance(30., Workplane200, Point301, Point302)
#And the distance from our line segment to the origin is 10.0 units.
Constraint.distance(10., Workplane200, Point101, Line400)
#And the line segment is vertical.
Constraint.vertical(Workplane200, Line400)
#And the distance from one endpoint to the origin is 15.0 units.
Constraint.distance(15., Workplane200, Point301, Point101)
if 0:
#And same for the other endpoint; so if you add this constraint then
#the sketch is overconstrained and will signal an error.
Constraint.distance(18., Workplane200, Point301, Point101)
#The arc and the circle have equal radius.
Constraint.equal_radius(Workplane200, Arc401, Circle402)
#The arc has radius 17.0 units.
Constraint.diameter(17.*2, Workplane200, Arc401)
#If the solver fails, then ask it to report which constraints caused
#the problem.
sys.calculateFaileds = 1
#And solve.
result = sys.solve()
if result == SLVS_RESULT_OKAY:
print("solved okay")
print("line from ({:.3f} {:.3f}) to ({:.3f} {:.3f})".format(
sys.get_param(7).val, sys.get_param(8).val,
sys.get_param(9).val, sys.get_param(10).val
))
print("arc center ({:.3f} {:.3f}) start ({:.3f} {:.3f}) finish ({:.3f} {:.3f})".format(
sys.get_param(11).val, sys.get_param(12).val,
sys.get_param(13).val, sys.get_param(14).val,
sys.get_param(15).val, sys.get_param(16).val
))
print("circle center ({:.3f} {:.3f}) radius {:.3f}".format(
sys.get_param(17).val, sys.get_param(18).val, sys.get_param(19).val
))
print("{} DOF".format(sys.dof))
else:
print("solve failed: problematic constraints are:")
for e in sys.faileds:
print(e)
if result == SLVS_RESULT_INCONSISTENT:
print("system inconsistent")
else:
print("system nonconvergent")
if __name__=='__main__':
#Example3d()
Example2d()
'''
solved okay
line from (10.000 11.180) to (10.000 -18.820)
arc center (101.114 119.042) start (116.477 111.762) finish (117.409 114.197)
circle center (200.000 200.000) radius 17.000
6 DOF
'''
Comments
comments powered by Disqus