FreeCAD-macros
256 строк · 9.0 Кб
1# -*- coding: utf-8 -*-
2
3# Copyright (c) 2020 Simone Bignetti, Gottolengo Italy (simone.b)
4#
5# This file is part of the FreeCAD CAx development system.
6#
7# This library is free software; you can redistribute it and/or
8# modify it under the terms of the GNU Lesser General Public
9# License as published by the Free Software Foundation; either
10# version 2.1 of the License, or (at your option) any later version.
11#
12# This library is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15# Lesser General Public License for more details.
16#
17# You should have received a copy of the GNU Lesser General Public
18# License along with this library; if not, write to the Free Software
19# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
20# USA
21#
22# You can contact me by mail at simone.bignetti@linux.it
23
24__Name__ = 'HilbertCurve'
25__Comment__ = 'This macro creates an Hilbert curve wire in 2 or 3 dimensions with many iterations.'
26__Author__ = 'Simone Bignetti'
27__Version__ = '1.2.0'
28__Date__ = '2020-12-29'
29__License__ = 'LGPL-2.1-or-later'
30__Web__ = 'https://wiki.freecadweb.org/Macro_HilbertCurve'
31__Wiki__ = 'https://wiki.freecadweb.org/Macro_HilbertCurve'
32__Icon__ = 'HilbertCurve.svg'
33__Help__ = 'Choose the dimensions of the wire, the number of the iterations and the length of the wire segment.'
34__Status__ = 'Stable'
35__Requires__ = ''
36__Communication__ = 'https://forum.freecadweb.org/viewtopic.php?f=22&t=53781'
37__Files__ = 'HilbertCurve.svg'
38
39
40# For the wire
41import FreeCAD as app
42import Draft
43
44# For the gui
45from PySide import QtGui, QtCore
46
47class HilbertCurve:
48"""The class of the Hilbert curve.
49
50By this class it's possible to create a wire
51of a fractal Hilbert curve with
52a fixed number of dimensions and iterations.
53"""
54
55def __init__(self, dimensions, iterations):
56"""Initialize the Hilbert curve.
57
58Args:
59iterations (int): iterations to use in constructing the curve
60dimensions (int): number of dimensions
61"""
62
63self.dimensions = dimensions
64self.iterations = iterations
65
66# minimum and maximum distance along curve
67self.min_distance = 0
68self.max_distance = 2 ** (self.iterations * self.dimensions) - 1
69
70# minimum and maximum coordinate value in any dimension
71self.min_coordinate = 0
72self.max_coordinate = 2 ** self.iterations - 1
73
74# number of points
75self.number_of_points = 2 ** (self.iterations * self.dimensions)
76
77def point_from_distance(self, distance):
78"""Return a point in n-dimensional space given a distance along a the curve.
79
80Args:
81distance (int): integer distance along the curve
82
83Returns:
84point (iterable of ints): an n-dimensional vector of length dimensions where
85each component value is between 0 and 2**iterations-1.
86"""
87
88bit_string = format(distance, 'b').zfill(self.iterations * self.dimensions) # zero filled binary distance
89point = [int(bit_string[i::self.dimensions], 2) for i in range(self.dimensions)] # transpose of distance
90
91# Gray decode: point = point xor (point / 2)
92gray = point[self.dimensions-1] >> 1
93for i in range(self.dimensions-1, 0, -1):
94point[i] ^= point[i-1]
95point[0] ^= gray
96
97# Undo excess work
98q = 2
99while q != (2 << (self.iterations-1)):
100p = q - 1
101for i in range(self.dimensions-1, -1, -1):
102if point[i] & q:
103# invert
104point[0] ^= p
105else:
106# exchange
107gray = (point[0] ^ point[i]) & p
108point[0] ^= gray
109point[i] ^= gray
110q <<= 1
111
112return point
113
114def get_min_distance(self):
115"""Return the minimum distance along the curve."""
116return self.min_distance
117
118def get_max_distance(self):
119"""Return the maximum distance along the curve."""
120return self.max_distance
121
122def get_min_coordinate(self):
123"""Return the minimum coordinate value in any dimension."""
124return self.min_coordinate
125
126def get_max_coordinate(self):
127"""Return the maximum coordinate value in any dimension."""
128return self.max_coordinate
129
130def get_number_of_points(self):
131"""Return the number of points in the curve."""
132return self.number_of_points
133
134def get_points(self):
135"""Return the list of points in the curve."""
136points = []
137for point_number in range(self.number_of_points):
138points.append(self.point_from_distance(point_number))
139return points
140
141def __str__(self):
142return f"HilbertCurve(dimensions={self.dimensions}, iterations={self.iterations})"
143
144def __repr__(self):
145return self.__str__()
146
147
148class Hilbert_Dialog(QtGui.QDialog):
149"""The dialog for the Hilbert curve
150
151This class opens in FreeCAD a dialog to input
152the number of dimensions and the number of iterations
153to create the Hilbert curve.
154OK creates the curve.
155CANCEL quit the macro.
156"""
157
158def __init__(self):
159super(Hilbert_Dialog, self).__init__()
160self.setupUi()
161
162def setupUi(self):
163self.dimensions = 2
164self.iterations = 3
165
166self.setGeometry(250, 250, 400, 300) # Window definition
167self.setWindowTitle("Hilbert curve Macro")
168
169titleLabel = QtGui.QLabel("Create an Hilbert curve in two or three dimensions") # Title and subtitle
170titleFont = QtGui.QFont()
171titleFont.setBold(True)
172titleFont.setWeight(75)
173titleLabel.setFont(titleFont)
174subtitleLabel = QtGui.QLabel("This macro creates a wire in the draft workbench\nwith the shape of an Hilbert curve in two or three dimensions.\nFor example, you can use this wire as a sweep path.\nIt's recommended to apply a radius at the wire,\nin order to obtain a correct sweep generation.")
175titleBox = QtGui.QVBoxLayout()
176titleBox.addWidget(titleLabel)
177titleBox.addWidget(subtitleLabel)
178titleBox.insertStretch(-1)
179
180dimensionsLabel = QtGui.QLabel("Number of dimensions: ") # Number of dimensions
181self.twoDradioButton = QtGui.QRadioButton()
182self.twoDradioButton.setText("2D")
183self.twoDradioButton.setChecked(True)
184self.threeDradioButton = QtGui.QRadioButton()
185self.threeDradioButton.setText("3D")
186dimensionsBox=QtGui.QHBoxLayout()
187dimensionsBox.addWidget(dimensionsLabel)
188dimensionsBox.addWidget(self.twoDradioButton)
189dimensionsBox.addWidget(self.threeDradioButton)
190
191iterationsLabel = QtGui.QLabel("Iterations:") # Iterations and length spins in a grid
192self.iterationsSpin = QtGui.QSpinBox()
193self.iterationsSpin.setMinimum(1)
194self.iterationsSpin.setMaximum(10)
195lengthLabel = QtGui.QLabel("Length:")
196self.lengthSpin = QtGui.QDoubleSpinBox()
197self.lengthSpin.setMinimum(1.0)
198self.lengthSpin.setMaximum(999999.000000000000000)
199self.lengthSpin.setValue(10.000000000000000)
200grid = QtGui.QGridLayout()
201grid.setSpacing(10)
202grid.addWidget(iterationsLabel, 1, 0)
203grid.addWidget(self.iterationsSpin, 1, 1)
204grid.addWidget(lengthLabel, 2, 0)
205grid.addWidget(self.lengthSpin, 2, 1)
206
207okButton = QtGui.QPushButton("OK") # Ok and Cancel Buttons at right bottom
208okButton.clicked.connect(self.onOkButton)
209cancelButton = QtGui.QPushButton("Cancel")
210cancelButton.clicked.connect(self.onCancelButton)
211buttonBox = QtGui.QHBoxLayout()
212buttonBox.addStretch()
213buttonBox.addWidget(okButton)
214buttonBox.addWidget(cancelButton)
215
216vbox = QtGui.QVBoxLayout()
217vbox.addLayout(titleBox)
218vbox.addLayout(dimensionsBox)
219vbox.addLayout(grid)
220vbox.addLayout(buttonBox)
221vbox.setSpacing(30)
222vbox.insertStretch(1)
223self.setLayout(vbox)
224
225self.show()
226
227def onOkButton(self):
228if self.twoDradioButton.isChecked():
229dimensions = 2
230else:
231dimensions = 3
232iterations = self.iterationsSpin.value()
233length = self.lengthSpin.value()
234HC=HilbertCurve(dimensions, iterations)
235points = HC.get_points()
236pl = app.Placement()
237pl.Rotation.Q = (0.0, 0.0, 0.0, 1.0)
238pl.Base = app.Vector(0.0, 0.0, 0.0)
239vectors = []
240if dimensions == 2:
241for point in points:
242vectors.append(app.Vector(point[0]*length, point[1]*length, 0.0))
243else:
244for point in points:
245vectors.append(app.Vector(point[0]*length, point[1]*length, point[2]*length))
246wire = Draft.makeWire(vectors, placement=pl, closed=False, face=False, support=None)
247wire.Label = "Hilbert"
248Draft.autogroup(wire)
249self.close()
250
251def onCancelButton(self):
252self.close()
253
254
255hilbert_dialog = Hilbert_Dialog()
256hilbert_dialog.exec()
257