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