├── .vscode
└── settings.json
├── LICENSE.md
├── README.md
├── cad
├── Spindle Holder.stl
├── Spindle.stl
├── cnc-mill.scad
└── spindle.scad
├── fj.toml
├── model
├── .gitignore
├── Cargo.toml
└── src
│ ├── lib.rs
│ ├── machine
│ ├── axes
│ │ ├── mod.rs
│ │ ├── y.rs
│ │ └── z.rs
│ ├── mod.rs
│ ├── rails.rs
│ └── spindle.rs
│ ├── physics.rs
│ └── tools.rs
└── rustfmt.toml
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true
3 | }
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | # Zero-Clause BSD License
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
4 |
5 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CNC Mill
2 |
3 | ## Goal
4 |
5 | I'd like to have a 3-axis CNC mill. I've considered buying one, but everything I can find violates one or more of the following constraints:
6 |
7 | - **Budget:** It's going to be my first CNC mill, so spending a lot of money doesn't make much sense. The main purpose is to learn, and figure out what I actually need in a CNC mill. For that reason, I'd like to keep the budget below 1000€.
8 | - **Capability:** At the same time, I don't want to buy a machine that I will outgrow within the first week. It should be capable of cutting aluminium.
9 | - **Space:** I'm severely limited on space. It needs to comfortably fit on a desk.
10 | - **Environment:** I don't have a proper workshop, unfortunately. The machine will need to run in my apartment, preferably without disturbing the neighbors.
11 |
12 | There simply doesn't seem to be a machine like this, which is not a surprise. For this reason, I'd like to try and build my own, purpose-made for my use case.
13 |
14 | I suspect it might simply be impossible to fulfill all of these requirements within the limited budget. If that is the case, I'm looking forward to learning what the limitations are specifically. I'm also aware that there's a high risk that a self-built machine will fall short in some or all of these areas. I think it's still likely to be a good investment, for the learning experience alone.
15 |
16 |
17 | ## Status
18 |
19 | This project has stalled while in the planning phase. I was at the point where the calculations I wanted to do were way beyond my knowledge, so I had to do quite a bit of studying to make progress. Then my daily schedule shifted from under me and I was left with very little time to continue doing that.
20 |
21 | I'm sure at some point, I'll have both the desire and the time to design my own CNC mill again. Whether I'll pick back up here when that time comes, or if I decide to start fresh, I don't know.
22 |
23 |
24 | ## License
25 |
26 | This project is open source. All documents, design files, and software in this repository are available under the terms of the [Zero Clause BSD License] (0BSD, for short). This basically means you can do anything with them, without any restrictions, but you can't hold the authors liable for problems.
27 |
28 | See [LICENSE.md] for full details.
29 |
30 | [Zero Clause BSD License]: https://opensource.org/licenses/0BSD
31 | [LICENSE.md]: LICENSE.md
32 |
33 |
34 | ## Design Decisions
35 |
36 | Here are some high-level design decisions I've made. None of them are final, and they might still change as I do more research:
37 |
38 | - **Configuration:** Fixed gantry. The advantages are just too big, and I think I can live with a smaller work area in one axis.
39 | - **Size:** 40x40x40 cm³ or thereabouts. Those are outer dimensions. If it turns out that this leaves not enough working area, I can go a bit larger, especially in height.
40 | - **Spindle:** 1.5 kW air-cooled AC spindle. Should be strong enough to do well in aluminium and avoids the additional complexity of water-cooling.
41 | - **Axis motors:** Stepper motors. I haven't really looked into servos, but seeing how many machines run just fine with steppers, I'm pretty confident they will work for me. An open-loop control system will also reduce cost and complexity.
42 |
43 |
44 | ## Parts
45 |
46 | - eBay
47 | - [Zhong Hua Jiang 1.5 kW Air-Cooled CNC Spindle Motor 80mm](https://www.zhonghuajiangspindle.com/1.5kw-cnc-air-cooled-spindle-motor-80mm.html): Those are widely available on eBay. Preferably ER16, but ER11 is acceptable.
48 | - 1.5 kW Huanjang VFD: there are sets of those and the aforementioned spindle available
49 | - Sorotec
50 | - [Spindle Clamp](https://www.sorotec.de/shop/Spindelhalter-f-r-80mm-HFS-Spindeln-4085.html): Lots of the spindle/VFD sets on eBay come with a clamp, but those don't have mounting holes. This one looks nicer.
51 |
52 |
53 | ## Research Notes
54 |
55 | These are the notes from my research process.
56 |
57 | ### Other Machines
58 |
59 | It's going to make sense to take inspiration from other machines at some point. Here are some links:
60 |
61 | - [**OpenBuilds**](https://openbuilds.com/): Lots of machines of all kinds.
62 | - [**MPCNC**](https://docs.v1engineering.com/mpcnc/intro/): Interesting design. Less traditional than what I have in mind.
63 | - [**Tormach xsTECH**](https://tormach.com/machines/routers/xstech-router.html): Really interesting machine, in terms of configuration, form factor, and enclosure. Pretty much what I want to build, but hopefully I can fit some more rigid components into the budget.
64 | - [**PrintNC**](https://wiki.printnc.info/en/home): Moving gantry design with a frame based on steel tubing.
65 | - [**Millennium Mill**](https://www.reddit.com/r/MilleniumMachines/): C-frame mill based on aluminium extrusion.
66 | - [**ULTIMATE Bee**](https://bulkman3d.com/knowledge-base/ultimate-bee/): Classic moving gantry design, with what looks like high-quality components.
67 | - [**PocketNC**](https://pocketnc.com/): 5-axis desktop CNC mill. Interesting, in that it is used for milling aluminium (easy to find examples on YouTube), but has a relatively weak spindle (200W).
68 | - [**Nomad**](https://carbide3d.com/nomad/): Pretty close to what I would like to build, in regards to the configuration, form factor, and enclosure. Notable for its weak spindle (70W).
69 |
70 | ### Online Shops
71 |
72 | I'm looking into two groups of online shops:
73 |
74 | - Local (i.e. European) shops, because that gives me a minimum of hassle (shipping, customs).
75 | - Shops on platforms like AliExpress or eBay with low prices, as that might be necessary to meet my budget.
76 |
77 | European shops:
78 |
79 | - [**cnc-technics:**](https://shop.cnc-technics.de/): Have lots of relevant products, but probably too high-end for my budget.
80 | - [**DOLD Mechatronik**](https://dold-mechatronik.de/): Have a lot of different stuff, including aluminium in various sizes. At least some categories seems to be high-priced, relative to the available budget.
81 | - [**Sorotec**](https://www.sorotec.de/shop/): Large selection of all kinds of stuff required for CNCs.
82 | - [**MISUMI**](https://de.misumi-ec.com/): Large selection of materials, mechanical components, and much more. Lots of options for modifying material and aluminium extrusion parts.
83 | - [**Motedis**](https://www.motedis.com/): Materials and mechanical components.
84 |
85 | AliExpress:
86 |
87 | - [**Bulk Man 3D**](https://www.aliexpress.com/store/1752067) ([also have a website](https://bulkman3d.com/)): Large selection of lots of things I'm going to need.
88 | - [**Makerbase**](https://www.aliexpress.com/store/1047297): Control boards mostly seem not applicable, but the stepper drivers are very interesting.
89 | - [**Zhong Hua Jiang**](https://de.aliexpress.com/store/214974): They manufacture spindles that they sell there, as well as other useful things.
90 |
91 | eBay:
92 |
93 | - [**motor-mall**](https://www.ebay.de/str/motormall): Chinese shop, ships from Europe. Has spindles and VFDs.
94 | - Those seems to be essentially the same:
95 | - [**kuku081**](https://www.ebay.de/str/kuku081)
96 | - [**kuku281**](https://www.ebay.de/str/kuku218)
97 | - [**kuku86**](https://www.ebay.de/str/kuku86)
98 | - [**powave21**](https://www.ebay.de/str/powace21)
99 | - [**rattmmotor**](https://www.ebay.de/str/rattmmotor)
100 | - [**rattmmotor88**](https://www.ebay.de/str/rattmmotor88)
101 | - [**RATTM MOTOR CNC**](https://www.ebay.de/str/rattmmotorcnc)
102 | - [**chinacnczone-de**](https://www.ebay.de/str/cnczonedd)
103 |
104 | ### Configuration
105 |
106 | I believe that the following machine configurations are the strongest contenders:
107 |
108 | - **Moving gantry**: Tool moves in all 3 axes.
109 | - **Fixed gantry**: Tool moves in x and z axes, table moves in y axis.
110 |
111 | I have ruled out more exotic configurations for my first build (despite having lots of ideas), to reduce overall risk.
112 |
113 | References:
114 |
115 | - https://cncchronicle.com/fixed_or_moving_gantry_for_cnc_router/
116 | Compares the two configurations. Has a nice comparison table further down.
117 |
118 | ### Spindle
119 |
120 | I see two potentially viable ways to go, regarding the spindle:
121 |
122 | - Cheaper DC spindle, around 500W.
123 | - Mid-range AC spindle, possibly water-cooled.
124 |
125 | #### DC Spindles
126 |
127 | Advantages of DC spindles:
128 |
129 | - There are examples of machines with relatively weak spindles that can definitely mill aluminium, although not fast and with not-so-great surface finish. It might be enough for my needs.
130 | - Cheaper than AC spindles.
131 | - More compact than AC spindles. That doesn't just go for the spindle itself, but also for the hardware needed to control it (a possibly bulky VFD, in the case of AC spindles). Given the size constraints, this is very attractive.
132 | - No dealing with AC power.
133 |
134 | Examples of machines with relatively weak DC spindles:
135 |
136 | - **PocketNC**: Both the V2-10 and the V2-50 come with a 200W spindle[^1][^2]. And yet it seems capable milling aluminium and more. This video is very interesting: https://youtu.be/7YfRNZbfjaY?t=326
137 | - **Nomad**: Only has a 70W spindle. It's easy to find videos of it milling aluminium, but in the one's I've seen, either the sound is covered by a voiceover, or it sounds horribly chattery. So not a strong case, but interesting none the less.
138 |
139 | I think the PocketNC is a strong example here. It's obviously not capable of great speeds, and it starts chattering if the settings are too aggressive. But still, it seems to be capable of producing aluminium parts.
140 |
141 | Examples of DC spindles:
142 |
143 | - [104W, 10.8k RPM, ER8, ~140€](https://www.ebay.de/itm/384842723224); includes driver and bracket
144 | - [400W, 12k RPM, ER8, ~130€](https://www.ebay.de/itm/255388710895); includes driver and bracket
145 | - [400W, 12k RPM, ER8, ~140€](https://www.ebay.de/itm/384842725572); includes driver and bracket
146 | - [500W, 12k RPM, ER11, ~120€](https://www.ebay.de/itm/403510912800); includes driver and bracket
147 | - [500W, 12k RPM, ER11, ~60€](https://www.ebay.de/itm/174570637963); includes driver and bracket
148 | - [600W, 12k RPM, ER11, ~85€](https://www.ebay.de/itm/384858278032); motor only
149 |
150 | [^1]: https://cdn.shopify.com/s/files/1/0077/5477/6623/files/V2-10_Spec_V05.pdf?v=1611173337
151 | [^2]: https://cdn.shopify.com/s/files/1/0077/5477/6623/files/V250CHKCHBSpecSheet.pdf?v=1624559427
152 |
153 | #### AC Spindles
154 |
155 | Advantages of AC spindles:
156 |
157 | - They are simply more powerful. I found information, that 1kW power and 24k max RPM should be good for milling aluminium, and I think everything in that range is an AC spindle.
158 | - Water-cooled AC spindles are pretty common. Those are quieter and more long-lived, and I've been told that water cooling isn't too bad, from a complexity perspective.
159 | - The Chinese ones are still surprisingly affordable, although I have no idea how the quality compares to more expensive ones.
160 |
161 | Notes:
162 |
163 | - In articles I've read, Huanyang has been called out as a quality brand, and I can find affordable offers with EU-based inventory. Unless the budget turns out to be really tight, or some other information comes to light, I might just go with that.
164 | - Some spindles come in a rectangular form factor, with mounting holes included. Seems more convenient than the round ones.
165 |
166 | Examples:
167 |
168 | - Air-cooled:
169 | - [0.8kW, 24k RPM, ER11, ~240€](https://www.ebay.de/itm/174956638303); VFD included
170 | - [0.8kW, 24k RPM, ER11, ~240€](https://www.ebay.de/itm/185102648864); VFD specified at 1.5kW
171 | - [2.2kW, 24k RPM, ER20, ~220€](https://www.ebay.de/itm/171841127523); VFD included
172 | - [2.2kW, 24k RPM, ER20, ~220€](https://www.ebay.de/itm/183100123168); VFD included; not sure, if there's any difference to the previous one
173 | - Water-cooled:
174 | - [1.5kW, 24k RPM, ER16, ~155€](https://www.ebay.de/itm/185467595787); spindle only
175 | - [2.2kW, 24k RPM, ER20, ~320€](https://www.ebay.de/itm/185340019340); full set: spindle, VFD, water pump, holder, collets
176 | - [2.2kW, 24k RPM, ER20, ~350€](https://www.ebay.de/itm/185467757422); VFD included
177 | - [3.0kW, 24k RPM, ER20, ~165€](https://www.ebay.de/itm/185102654786); spindle only
178 | - [3.0kW, 24k RPM, ER20, ~285€](https://www.ebay.de/itm/174974637566); VFD included
179 |
180 | #### References
181 |
182 | - https://mellowpine.com/cnc/how-to-choose-a-cnc-spindle/
183 | Has very specific recommendations.
184 | - https://mellowpine.com/cnc/best-cnc-spindles/
185 | Presents some specific spindles and what they're suitable for.
186 | - https://en.wikipedia.org/wiki/Collet#ER_collets
187 | Just some background info on ER collets, for the mechanically challenged (like me).
188 | - https://www.youtube.com/watch?v=w26DHMccicE
189 | Upgrades to a 3018. Performance becomes satisfactory with a ~1kW spindle. 500W works, but is really slow.
190 |
191 | #### Conclusion
192 |
193 | Based on everything I've seen, I've narrowed the spindle selection down to the following criteria:
194 |
195 | - 1KW+: Yes, you can mill aluminium with less, but it's far from certain whether the results would be satisfying.
196 | - 24,000 max. RPM: This should be sufficient for my needs, and it seems that within the category of 1kW+, this doesn't further reduce the selection.
197 | - Air-cooled: I can find a few (very few) water-cooled ER16 spindles on eBay, but at this point, I'm happy to accept the reduced complexity. Any difference in noise is probably irrelevant anyway, compared to the milling noise itself.
198 | - AC: I can't find any DC spindles that are powerful enough, so it's not a choice at this point.
199 |
200 | Most of the spindles I can find with these parameters are from this manufacturer: [Zhong Hua Jian](https://www.zhonghuajiangspindle.com/)
201 |
202 | They have a square spindle that I would prefer: [1.5KW 1500W Air Cooled Spindle Square CNC Spindle Motor ER11/ER16](https://www.zhonghuajiangspindle.com/1.5kw-cnc-square-air-cooled-spindle-motor.html)
203 |
204 | Unfortunately that's the one that I can't find a source for around here.
205 |
206 | ### Cutting Tools
207 |
208 | The cutting tools that are expected to be used are important for various design decisions. Since I don't have very specific use cases in mind, my goal here is to get a feel for the range of tooling that I might want to use for aluminium. From there, I'll hopefully have better information to make various design decisions.
209 |
210 | Here are some cutting tools that I could find:
211 |
212 | - cnc-technics
213 | - Endmills: https://shop.cnc-technics.de/Fraeser/?view_mode=default&listing_sort=&listing_count=96
214 | - Dold Mechatronik
215 | - Drills: https://www.dold-mechatronik.de/Werkzeuge-Bohrer
216 | - Endmills: https://www.dold-mechatronik.de/VHM-Fraeser
217 | - Sorotec: https://www.sorotec.de/shop/Zerspanungswerkzeuge/
218 | - Sorotec: https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/
219 | - Firstattec: https://www.sorotec.de/shop/Zerspanungswerkzeuge/Firstattec/
220 | - Datron: https://www.sorotec.de/shop/Zerspanungswerkzeuge/datron-cnc-fraeswerkzeuge/
221 | - Datron: https://webshop.datron.de/industrie-fraeswerkzeuge/?p=1
222 | - Misumi: https://de.misumi-ec.com/vona2/fs_machining/
223 |
224 | I've decided to focus my analysis on the Sorotec offerings, as they have a broad selection, and also focus on the class of machine I'm trying to build, roughly. I'm only looking at tools that are specifically recommended for aluminium.
225 |
226 | Analysis:
227 |
228 | - Diameter, cutting (mm): 0.2 - 60
229 | - Diameter, shaft (mm): 3 - 12
230 | - Length, cutting (mm): 2 - 43
231 | - Length, total (mm): 38 - 110
232 |
233 | List of tools taken into account:
234 |
235 | - Countersinking
236 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/Entgrater---Senker/HM-Senker/
237 | - Deburring/Chamfering/Filleting
238 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/Entgrater---Senker/Entgratfraeser-Fasenfraeser/
239 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/Entgrater---Senker/Viertelkreisfraeser/
240 | - Drilling
241 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/HM-Bohrer/bohrer3175/
242 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/HM-Bohrer/bohrer317565/
243 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/vhm-spiralbohrer/
244 | - Engraving
245 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/Gravurwerkzeuge/Gravierfraeser/
246 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/Gravurwerkzeuge/Gravierstichel--Standard-/
247 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/Gravurwerkzeuge/Radienstichel/
248 | - Face Milling
249 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/planfraeser-985/Planfraeser/
250 | - Finishing
251 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/schlichtfraeser/schlichtfraeser-beschichtet/
252 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/schlichtfraeser/schlichtfraeser-unbeschichtet/
253 | - Milling
254 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/3-175----1-8---Fraeser/2-Schneider-ALU/
255 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/3-175----1-8---Fraeser/Sonderlaengen-360/2-Schneider-361/
256 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-schneider/Schaftfraeser-ALU-412/
257 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-schneider/1-Schneider-Sorotec-PROALU/
258 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-schneider/einschneider-sorotec-alu-beschichtet/
259 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/2-schneider/2-schneider-kurz/
260 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/2-schneider/2-schneider-alu-eckradius/
261 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/2-schneider/Schaftfraeser-ALU/
262 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/RADIENFRAeSER/1-Schneider-PRO/
263 | - Tapping
264 | - https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/Gewindefraeser/
265 |
266 | Additional notes:
267 |
268 | - https://www.sorotec.de/webshop/Datenblaetter/fraeser/schnittwerte.pdf
269 | - https://webseite.sorotec.de/download/fraesparameter/schnittwerte_1_8_sv.pdf
270 | - https://www.sorotec.de/webshop/Datenblaetter/fraeser/fraeser_verwendung_schaftfraeser.png
271 | - https://webseite.sorotec.de/download/fraesparameter/schnittwerte_planfraeser.pdf
272 |
273 | ### Cutting Forces
274 |
275 | *The following is based on some research, and my own not-that-great knowledge about mechanics. For all I know, my complete reasoning could be wrong.*
276 |
277 | My thinking is that once I've selected a spindle, I can calculate what maximum cutting force can be achieved using that spindle. With that information, I can then decide how to dimension the linear axes and the frame.
278 |
279 | Some reference material on how to calculate cutting force:
280 |
281 | - [Cutting Forces in Milling](https://www.ame.com/workholding-wisdom-posts/2021/03/01/cutting-forces-in-milling/)
282 | - [Understanding tangential cutting force when milling](https://www.ctemag.com/news/articles/understanding-tangential-cutting-force-when-milling)
283 | - [Lots of useful formula for milling](https://www.sandvik.coromant.com/en-gb/knowledge/machining-formulas-definitions/pages/milling.aspx)
284 |
285 | From the aforementioned references, I got the following formula for computing tangential cutting force:
286 |
287 | $F_t = \frac{T_s}{R}$
288 |
289 | Where:
290 | - $F_t$: tangential cutting force
291 | - $T_S$: spindle torque
292 | - $R$: cutter radius
293 |
294 | Which makes a lot of sense. I can hopefully get the maximum torque rating of the spindle from the manufacturer. If not, I'm sure there's a way to calculate torque from spindle power, but I haven't looked into how to do that.
295 |
296 | Given the maximum torque, I can put in the radius of the smallest tool I intend to use. It's quite possible that the resulting force will be larger than the small tool can withstand. But I can just put in larger and larger tools until I get a force that will actually work with the tool, thereby figuring out the maximum tangential cutting force that is actually realistic.
297 |
298 | Once I have that number, I need two more:
299 |
300 | - Maximum expected tool length
301 | - Distance between tip of maximum length tool and spindle holder.
302 |
303 | With that, I can calculate the moment that acts on the spindle, which I can then use to figure out how to dimension the linear hardware on the z and x axes.
304 |
305 | *At this point I'm realizing that it would be easier to just do the calculations instead of spelling out how I would do them. I can't though, because I haven't selected a spindle yet, so I'll continue writing out my thoughts on this, so I don't forget between now and when I'll actually do the calculations. (When I started writing this, I just expected there would be much more research and much less reasoning.)*
306 |
307 | Side note: It's interesting to note that the worst case for max. tangential cutting force is a thin tool, and worst case for max. moment on the spindle is a long tool. It's probably a good idea to plug in multiple tools into the whole calculation, to figure out where the actual worst case is, since the thinnest tool certainly won't be the longest.
308 |
309 | Based on the max. tangential cutting force, I can also figure out the maximum moment that acts on the y axis, and dimension the linear hardware for that too.
310 |
311 | I assume with all those moments calculated, I can then figure out what the frame would need to look like to not deflect too much under that level of stress. I don't know how that works though.
312 |
313 | ### Linear Guides
314 |
315 | I figure that within the constraints of a home-built machine, linear rails are the best I can practically do in this category. I've decided to focus my research on those, and see where that leaves me budget-wise. I'll revisit later, if necessary.
316 |
317 | Information from online shops:
318 |
319 | - ARC/HRC: https://www.dold-mechatronik.de/Profilschienenfuehrungen-ARC-HRC
320 | - data sheet: https://www.dold-mechatronik.de/documents/Datenblaetter/Linearfuehrungen/Datenblatt-Linearfuehrung-ARC-HRC.pdf
321 | - seal (Dichtung): S or B
322 | - S: seals better; recommended for dirty environments.
323 | - B: less friction
324 | - preload (Vorspannung): VC, V0, V1, V2
325 | - VC and V0 have play; only available in lower quality classes (H, N)
326 | - V1 and V2 don't, but have more friction
327 | - V1 might be a good compromise, is my initial impression
328 | - HRC has higher tension than ARC, in the equivalent classes
329 | - precision class (Genauigkeitsklassen): N, H, P, SP, UP
330 | - recommended for CNC mills: N to P, or H to SP for more precision
331 | - bearing cage (Kugelkette): should make sure it's included, unless price is prohibitive
332 | - standard lengths, minimum: 300mm
333 | - series:
334 | - ARC-M: compact, narrow
335 | - I assume I'll have two rails in every axis, so narrow carriages should be fine. I assume broader ones make sense, if you have just one rail.
336 | - ARC-F: compact, flange
337 | - broader than M
338 | - HRC-M: high, narrow
339 | - more height above the rail
340 | - HRC-F: high, flange
341 | - relates to HRC-M as ARC-F relates to ARC-M
342 | - sub-series: Each of the previously presented series is further divided into more series. The distinction between those is the size of the carriage that fits a given size rail. Larger ones can take more force.
343 | - ARC-MS (S = small?): rail sizes from 15x15 to 28x27
344 | - ARC-MN (N = normal?): rail sizes from 15x15 to 53x46
345 | - ARC-ML (L = large?): rail sizes from 15x15 to 53x46
346 | - BR: https://www.dold-mechatronik.de/Profilschienenfuehrungen-BR-Serie
347 | - Can't find a data sheet.
348 | - BR9 and BR12 available.
349 | - Only BR12 has some dimensions in the shop. Smaller compared to even the smallest ARC/HRC.
350 | - Sold in sets of one rail + 2 carriages, which is what I need anyway.
351 | - Probably not interesting to me, given the small size and lack of information compared to ARC/HRC.
352 | - HG:
353 | - shop links:
354 | - DOLD Mechatronik
355 | - https://www.dold-mechatronik.de/HG-Profilschienenfuehrungen
356 | - HIWIN
357 | - https://www.sorotec.de/shop/CNC-Mechanik/lineartechnik/Lineartechnik--Hiwin/Fuehrungswagen/Baureihe-HGH-247/
358 | - https://www.sorotec.de/shop/CNC-Mechanik/lineartechnik/Lineartechnik--Hiwin/Fuehrungswagen/Baureihe-HGW/
359 | - https://www.sorotec.de/shop/CNC-Mechanik/lineartechnik/Lineartechnik--Hiwin/Fuehrungsschienen/Baureihe-HGH/
360 | - Sorotec
361 | - https://www.sorotec.de/shop/CNC-Mechanik/lineartechnik/sorotec-blue-line-936/
362 | - data sheet: https://www.dold-mechatronik.de/documents/Datenblaetter/Linearfuehrungen/Datasheet_HGH-HGW_de_210720_Dold.pdf
363 | - DOLD Mechatronik data sheet is much less detailed than ARC/HRC
364 | - HIWIN has much more detailed information. See below.
365 | - Size seems comparable to lower range of ARC/HRC, which I think fits my use case.
366 | - LF: https://www.dold-mechatronik.de/Profilschienenfuehrungen-LF-Serie
367 | - Sizes seem comparable to lower end of ARC/HRC.
368 | - Barely anything available at DOLD Mechatronik.
369 | - The available rails are way too long.
370 | - Not data sheet.
371 | - LSK: https://www.dold-mechatronik.de/Profilschienenfuehrungen-LSK-Serie
372 | - data sheet: https://www.dold-mechatronik.de/documents/Dold_LSK_Linearfuehrungen.pdf
373 | - Sizes seem comparable to lower end of ARC/HRC.
374 | - FL and GL recommended for machine tools.
375 | - FL seems lower, possible better suited.
376 | - pretention (Vorspannung): Z2 (hightest) recommended for machine tools
377 | - more information in there I didn't go through in detail
378 | - unclear how this is meaningfully different from ARC/HRC
379 | - MGN:
380 | - shop links:
381 | - DOLD Mechatronik
382 | - https://www.dold-mechatronik.de/MGN-Miniatur-Linearfuehrungen-schmal
383 | - Sorotec
384 | - https://www.sorotec.de/shop/CNC-Mechanik/lineartechnik/Lineartechnik--Hiwin/Fuehrungswagen/mgn-baureihe/
385 | - https://www.sorotec.de/shop/CNC-Mechanik/lineartechnik/Lineartechnik--Hiwin/Fuehrungsschienen/baureihe-mgn-r/
386 | - much smaller; probably more suited to low-force stuff like printers
387 | - MGW: https://www.dold-mechatronik.de/MGW-Miniatur-Linearfuehrungen-breit
388 | - MGN, but broad
389 | - probably unnecessary, compared to MGN, since I'm going for two rails per axis
390 | - MR: https://www.dold-mechatronik.de/MR-Miniatur-Linearfuehrungen
391 | - data sheet: https://www.dold-mechatronik.de/documents/Datenblaetter/Linearfuehrungen/Datenblatt-Miniaturlinearfuehrung-MR.pdf
392 | - very small, comparable to MGN/MGW
393 | - nice data sheet, didn't look into it in detail
394 | - didn't see anything about applications; probably too small for a mill
395 | - MRW: https://www.dold-mechatronik.de/MRW-Miniatur-Linearfuehrungen-breit
396 | - same data sheet as MR
397 | - wide version of MR
398 | - MSB: https://www.dold-mechatronik.de/Profilschienen-MSB-Serie-PMI
399 | - data sheet: https://www.dold-mechatronik.de/documents/Datenblaetter/Linearfuehrungen/MSB-TE-E.pdf
400 | - data sheet is sparse
401 | - size comparable with lower end of ARC/HRC
402 | - ST: https://www.dold-mechatronik.de/ST-Miniatur-Kurzhub-Linearfuehrung
403 | - shares a data sheet with MRW
404 | - specialized thing for short movements; probably not interesting
405 |
406 | Information from HIWIN:
407 |
408 | - full catalogue with lots of information:
409 | - https://www.hiwin.de/medias/GW-11-4-EN-2207-K.PDF?context=bWFzdGVyfGhpd2luRG9jdW1lbnRNZWRpYXw3NjY2NzMwfGFwcGxpY2F0aW9uL3BkZnxoaXdpbkRvY3VtZW50TWVkaWEvaDZkL2hkNi85MjcwNzkzNTY4Mjg2LnBkZnxmMjBjMGQ1ZjFjODA0ZjY5ODQ1Y2UwZWY5NGY5YWIzZWIxNGUzZTU3M2JjMmQ5OGVlOTMzZjQ4YmE3YjU5YmJh&attachment=true
410 | - https://www.hiwin.de/medias/GW-11-4-DE-2207-K.PDF?context=bWFzdGVyfGhpd2luRG9jdW1lbnRNZWRpYXw4MjA1ODI3fGFwcGxpY2F0aW9uL3BkZnxoaXdpbkRvY3VtZW50TWVkaWEvaDZiL2g2Ni85MjcwNzkzNTM1NTE4LnBkZnwzNDVmMDIxNDRjNDY4ZDUxOTVmNTgzN2M5ZjgwNzAwYzRlMGEyNmVlNmRjOGRlOWViZGQ0ZWI0OTMyMGI2N2U0&attachment=true
411 | - configurator: https://www.hiwin.de/configurator/newConfiguration/easyKAT_GW
412 |
413 | Conclusions:
414 |
415 | - ARC and HG seem like good options, given the size and available documentation.
416 | - Especially the HIWIN documentation is nice. But in the Sorotec store, HIWIN is also twice as expensive as the Sorotec-branded stuff.
417 |
418 | I would need, per axis:
419 |
420 | - 2 rails
421 | - 4 carriages
422 |
423 | So 6 rails and 12 carriages overall.
424 |
425 | Open questions:
426 |
427 | - What type should I go with?
428 | - Is HIWIN even an option price-wise? Would it be even worth it? Go for something cheaper?
429 | - What size rail?
430 |
431 | ### Ball Screws
432 |
433 | Much like with linear rails, I figure that the best I can practically do on linear actuators are ball screws. I will do my research on that, and might scale back to something cheaper, if it turns out to be necessary.
434 |
435 | References:
436 |
437 | - Catalogue from HIWIN
438 | - https://www.hiwin.de/medias/BS-08-10-DE-2206-K.PDF?context=bWFzdGVyfGhpd2luRG9jdW1lbnRNZWRpYXw2Mjg5NTAzfGFwcGxpY2F0aW9uL3BkZnxoaXdpbkRvY3VtZW50TWVkaWEvaDg3L2hmYi85MjcwMzA0NDczMTE4LnBkZnxlMzgzMWVjMzQ0NTAxOTQ2NWViYjJiMDQwN2VkMTI4MjdhYTNhNWViMTU5MGJmNGFmNDA1MjA2MWVjNGZiMzlm&attachment=true
439 | - https://www.hiwin.de/medias/BS-08-10-EN-2206-K.PDF?context=bWFzdGVyfGhpd2luRG9jdW1lbnRNZWRpYXw2MzM0NjAyfGFwcGxpY2F0aW9uL3BkZnxoaXdpbkRvY3VtZW50TWVkaWEvaGViL2g5Mi85MjcwMzA0NTA1ODg2LnBkZnw1Yzk3MzcxZTc1YmUyZTgzYjJmMWQxOGJkYzI1YTNjNTFkNGQwOWY1MGE3MGVjODg2OWNjNjBmNGI5NTFmNjBh&attachment=true
440 |
441 | Notes:
442 |
443 | - HIWIN:
444 | - types
445 | - rolled
446 | - diameter: 8-63mm
447 | - typical application: transportation
448 | - accuracy: T5 - T10
449 | - less friction and quieter than standard threads
450 | - nuts
451 | - FSIDIN, FSCDIN: flange
452 | - RSI, RSIT: no flange; probably wrong for my use case
453 | - peeled
454 | - diameter: 16-80mm
455 | - typical application: transportation + positioning
456 | - accuracy: T5 + T7
457 | - nuts
458 | - DEB-x
459 | - flange
460 | - single nut
461 | - variants with different kinds of wipers
462 | - DDB-x
463 | - flange
464 | - double nut
465 | - variants with different kinds of wipers
466 | - ZE, SE
467 | - no flange
468 | - probably wrong for my use case
469 | - SEM
470 | - safety nut
471 | - has some redundancy; correct function guaranteed up until certain play
472 | - pretty sure I don't need this
473 | - ground
474 | - diameter: 6-100mm
475 | - typical application: positioning
476 | - accuracy: T0 - T5
477 | - no stock; only available upon request
478 | - nuts
479 | - FSC
480 | - flange
481 | - single nut
482 | - cassette recirculation
483 | - FDC
484 | - flange
485 | - double nut
486 | - cassette recirculation
487 | - FSI
488 | - flange
489 | - single nut
490 | - internal recirculation
491 | - FDI
492 | - flange
493 | - double nut
494 | - internal recirculation
495 | - RSI, RDI
496 | - no flange
497 | - probably unsuited
498 | - other, seemingly more specialized types available
499 | - preload needs to be balanced
500 | - too low: lacks rigidity
501 | - too high: more friction, reduced service life
502 | - selection: page 12 of the HIWIN catalogue has a step-by-step guide
503 | - ball recirculation systems
504 | - external: tube outside of the nut body
505 | - internal: tube within the nut
506 | - cassette: internal, but somehow different? don't understand the description
507 | - wipers
508 | - NBR (N): "used in almost all applications"
509 | - NBR-finger (K): more friction, more resistance against dirt and chemicals
510 | - felt (F), felt-finger (V): different attributes, but unclear on applications
511 | - precision
512 | - T0 is best; don't have a feel for the numbers
513 | - the better the accuracy, the more limited length is; not relevant for my application
514 | - recommendations for milling
515 | - x/y axes: T1-T5
516 | - z axis: T2-T5
517 | - play/preload
518 | - rolled and peeled are delivered with play by default
519 | - types of preload
520 | - preloaded single nut: ball size
521 | - lead offset: not suited for high preloads or high leads
522 | - preloaded double nuts: distance between nuts
523 | - preload should not exceed
524 | - 5% of dynamic load rating for single nuts
525 | - 10% of dynamic load rating for double nuts
526 | - should only be preloaded when absolutely necessary
527 | - load rating
528 | - Cdyn: load at which 90% of ball screws reach life expectancy of 1 million revolutions
529 | - C0: load which causes permanent deformation of more than 0.0001 ball diameter
530 | - drive torque: calculation formulas on page 28
531 | - shaft ends
532 | - B, E, F: simple transport applications, low axial forces
533 | - SFA, SLA: more challenging precision applications
534 | - SLA: supported bearing (S1, S11, S5)
535 | - for supporting the non-motor end of the ball screw?
536 | - SFA
537 | - fixed bearing (S2, S22, S3)
538 | - for supporting the motor end of the ball screw?
539 | - WBK: heavy duty
540 | - housing for flange nuts
541 | - suitable for DEB-x, DDB-x, FSCDIN
542 |
543 | Looks like I need, per axis:
544 | - ball screw
545 | - flanged nut
546 | - supported bearing
547 | - fixed bearing
548 | - flanged nut housing
549 |
550 | Other than that, not sure yet, what conclusions to draw from all this.
551 |
552 | Open questions:
553 |
554 | - What size screw do I go with?
555 | - What accuracy do I need?
556 | - HIWIN is probably too expensive. Do cheaper options require different research?
557 | - What about preload? The HIWIN data sheet makes it sound like preload should be avoided, if at all possible. But isn't the whole point of a ball screw to avoid backlash?
558 |
559 |
560 | ### Frame
561 |
562 | Warning: This section is more of a brain dump, and not based on actual research so far. I'm drawing my own conclusions based on things I've seen in the past, and I'm probably missing something.
563 |
564 | I'm mainly thinking about two ways of building the frame:
565 |
566 | - Aluminium extrusion
567 | - Aluminium plates
568 |
569 | Or a combination of the two. Aluminium extrusion has the advantage of being more flexible and easier to use, while plates would require at least somewhat accurate drilling, if not machining.
570 |
571 | To make use of the full flexibility and simplicity of aluminium extrusion though, I need angle brackets to connect them. I don't know how well that would fare in regards to stiffness. I've seen people connect profiles by drilling through one, and putting in a screw that screws in the end of the other, but that would require somewhat accurate drilling again, plus cutting the profiles to length precisely.
572 |
573 | I'm guessing connecting plates with screws would be plenty stiff, and I could probably buy them already cut to size. This would only leave the problem of drilling and tapping, which might be manageable. An advantage over extrusion might be that they are just denser. Since more weight is largely good in a machine frame, and space is at a premium, that would probably be an advantage.
574 |
575 | At this point I'm thinking, I'll probably do a CAD design based on plates and see where that leads. If it seems doable, I might go that way. If not, I might reconsider doing an extrusion-based design.
576 |
577 |
578 | ### Stepper Drivers
579 |
580 | Makerbase: https://www.aliexpress.com/store/1047297
581 | - standard(?) breakout board pinout
582 | - Makerbase MKS A4988: https://www.aliexpress.com/item/32888457440.html
583 | 2.0A max
584 | - Makerbase MKS TMC2208: https://www.aliexpress.com/item/32888980385.html
585 | 2.0A max
586 | - Makerbase MKS TMC2209: https://www.aliexpress.com/item/33043140087.html
587 | 2.5A max
588 | - Makerbase MKS TMC2225: https://www.aliexpress.com/item/4001149124672.html
589 | 2.0A max
590 | - Makerbase MKS TMC2226: https://www.aliexpress.com/item/1005002669282600.html
591 | 2.5A max
592 | - Makerbase MKS TMC2160: https://www.aliexpress.com/item/1005004044381878.html
593 | 4.33A max
594 | - Makerbase MKS TMC2160-OC: https://www.aliexpress.com/item/4000185818422.html
595 | 4.33A max, extra cooling
596 |
597 | ### Control Software
598 |
599 | On the controller side I have the following priorities:
600 |
601 | - Resist the temptation to do any custom software, or otherwise use something exotic. This might be an option in later builds, but I want to keep it simple for the first version.
602 | - Be controllable from a regular PC, without requiring one to run. I don't want to add a screen/keyboard/mouse to the bill of materials, but I also don't want to dedicate a whole computer to control it.
603 |
604 | I found the following options:
605 |
606 | - [**LinuxCNC**](https://linuxcnc.org/): Runs on the Raspberry Pi:
607 | http://linuxcnc.org/docs/stable/html/getting-started/getting-linuxcnc.html
608 |
609 | The download page talks about interface cards. Not sure what specifically is required, but I found this list:
610 | http://wiki.linuxcnc.org/cgi-bin/wiki.pl?LinuxCNC_Supported_Hardware
611 |
612 | I haven't done much research, but the interface cards I saw were quite expensive. Overall, I get the impression that LinuxCNC is not suited for a budget-sensitive build.
613 |
614 | - [**Machinekit**](https://www.machinekit.io/): I found it hard to understand whether this is suitable. What I can gather is that it runs on the BeagleBone Black: https://www.machinekit.io/docs/getting-started/machinekit-images-for-bbb/
615 |
616 | No idea what else is required to make it work. The information presented is not very approachable, and I'm not sure how much of it is outdated.
617 |
618 | - [**grbl**](https://github.com/gnea/grbl): I've often heard about this being used for the kind of small-scale CNC machine I'm aiming to build. It's confusing though. There are two different repositories, neither actively developed, and lots of forks.
619 |
620 | - [**grblHAL**](https://www.grbl.org/): Fork of gbrl, for 32-bit MCUs.
621 |
622 | - [**FluidNC**](https://github.com/bdring/FluidNC): Looks promising. Has a list of supported hardware: https://github.com/bdring/FluidNC/wiki/Hardware-that-Runs-FluidNC
623 |
624 | Based on my cursory research into this area, I think this might be the wrong approach. It might be better to search for easily available CNC controllers, and figure out which software to use for them from there.
625 |
626 | Host-side control software:
627 | - https://github.com/Denvi/Candle
628 | - https://github.com/winder/Universal-G-Code-Sender
629 | - https://github.com/terjeio/ioSender
630 |
631 | ### Controller Boards
632 |
633 | I'm currently operating under the assumption that an open-loop control system using stepper motors will be used, for cost reasons.
634 |
635 | There is a huge number of options available. A lot of them are pricy enough to take up the majority of the available budget. For that reason, I'm focusing on lower-priced options.
636 |
637 | TinyG control boards, which include steppers drivers, but lack something to control the spindle:
638 |
639 | - https://synthetos.myshopify.com/products/tinyg
640 | Not the cheapest option ($129.99), but open source and well-documented. Includes 4 stepper drivers (2.5 amps).
641 | - https://synthetos.myshopify.com/collections/assembled-electronics/products/gshield-v5
642 | Cheaper alternative to the TinyG ($49.99), which requires an Arduino to work. I happen to have an Arduino Due lying around, so that might work out well. Includes 3 stepper drivers (2.5 amps).
643 |
644 | Both of those boards don't seem to be available in Europe, so the real cost might be significantly higher, with shipping and customs duties.
645 |
646 | SainSmart sells the controller boards for their low-cost CNC machines separately:
647 | - https://www.sainsmart.com/collections/genmitsu-cnc-replacement-upgrade-parts/products/genmtisu-grbl-controller-board-for-3018-prover-3018-mx3
648 | - https://www.sainsmart.com/collections/genmitsu-cnc-replacement-upgrade-parts/products/controller-board-for-genmtisu-cnc-router-3018-3018-pro-1810-rpo
649 |
650 | They are much cheaper (40-50€) and can control a spindle. One of them is limited to 1.5-2amps for the stepper motors, the other doesn't specify. Since the SainSmart machines are pretty weak, it's doubtful that the controller board supports any motors (axis or spindle) that would be a significant upgrade over them.
651 |
652 | Here's another board, linked to grblHAL:
653 | https://www.tindie.com/products/philba/grblhal-bob-unkit-for-teensy-41-t41u5xbb/
654 |
655 | Costs around 50€, plus shipping from the US. Requires a Teensy 4.1, which is readily available locally, for under 30€. Doesn't include stepper drivers, but can control a spindler with an external VFD.
656 |
657 | References:
658 |
659 | - https://www.cnccookbook.com/cnc-controller-software-drivers-boards/
660 | Overview over some available options.
661 |
662 | ### Power Supply
663 |
664 | As far as I can see, the machine needs the following kinds of power:
665 |
666 | - **230V AC**: Since I'm in Europe, this is the input I'm dealing with. It's needed by the VFD for the spindle, the DC power supply, possibly the water pump.
667 | - **3.3V - 5V DC**: This is the typical range for microcontrollers and many other kinds of electronics.
668 | - **Higher-voltage DC**: The stepper motors are going to need DC at a higher voltage than the controller.
669 |
670 | So I'm going to need a power supply that turns AC into DC, and possibly something else to step the DC up or down to meet the different requirements. As for the power supply, I've often seen [Meanwell](https://www.meanwell.com/) in various sets and such. Unless there's a good reason not to, I might just stick to that.
671 |
672 | ### CAD Software
673 |
674 | I'd love to use [Fornjot](https://www.fornjot.app/), but it'll be a while before it's ready for a project like this. I hope that I can migrate the project to Fornjot eventually.
675 |
676 | Normally, I'd favor open source software, but since migration to Fornjot at a later stage is planned anyway, I don't see that as a priority. I've used OpenSCAD, FreeCAD, SolveSpace, and other open source options in the past, and I'd like to take this opportunity to try something new.
677 |
678 | I'm focusing the research on options that are free (or have a free tier) and support Linux:
679 |
680 | - [**OnShape**](https://www.onshape.com/): Looks highly professional. Would certainly be an interesting learning experience.
681 | - [**SketchUp**](https://sketchup.com/): Also looks interesting, although the website presents architecture and furniture use cases, versus the mechanical assemblies showcased on the OnShape website. Hard to say how relevant this will be for this project.
682 | - [**TinkerCAD**](https://www.tinkercad.com/): Looks least interesting, judging from the website, as it stresses the beginner/education use case. Doesn't mean that it won't be more than capable enough for this project though.
683 |
684 | ### Enclosure
685 |
686 | The design goal for the whole machine is to have it run in an apartment environment, if at all possible, without creating the kind of noise that would be unacceptable in such an environment. An enclosure is going to be critical to achieve that.
687 |
688 | References:
689 |
690 | - https://www.youtube.com/watch?v=1zIFWG3X1DU
691 |
--------------------------------------------------------------------------------
/cad/Spindle Holder.stl:
--------------------------------------------------------------------------------
1 | solid Mesh
2 | facet normal -0.998533 -0.0541387 0
3 | outer loop
4 | vertex 40 -50 -37.5
5 | vertex 40 -50 37.5
6 | vertex 39.7655 -45.6752 -37.5
7 | endloop
8 | endfacet
9 | facet normal -0.998533 -0.0541387 0
10 | outer loop
11 | vertex 40 -50 37.5
12 | vertex 39.7655 -45.6752 37.5
13 | vertex 39.7655 -45.6752 -37.5
14 | endloop
15 | endfacet
16 | facet normal -0.986826 -0.161782 0
17 | outer loop
18 | vertex 39.7655 -45.6752 -37.5
19 | vertex 39.7655 -45.6752 37.5
20 | vertex 39.0648 -41.4012 -37.5
21 | endloop
22 | endfacet
23 | facet normal -0.986826 -0.161782 0
24 | outer loop
25 | vertex 39.7655 -45.6752 37.5
26 | vertex 39.0648 -41.4012 37.5
27 | vertex 39.0648 -41.4012 -37.5
28 | endloop
29 | endfacet
30 | facet normal -0.96355 -0.267529 0
31 | outer loop
32 | vertex 39.0648 -41.4012 -37.5
33 | vertex 39.0648 -41.4012 37.5
34 | vertex 37.9061 -37.2279 -37.5
35 | endloop
36 | endfacet
37 | facet normal -0.96355 -0.267529 0
38 | outer loop
39 | vertex 39.0648 -41.4012 37.5
40 | vertex 37.9061 -37.2279 37.5
41 | vertex 37.9061 -37.2279 -37.5
42 | endloop
43 | endfacet
44 | facet normal -0.928977 -0.370138 0
45 | outer loop
46 | vertex 37.9061 -37.2279 -37.5
47 | vertex 37.9061 -37.2279 37.5
48 | vertex 36.303 -33.2044 -37.5
49 | endloop
50 | endfacet
51 | facet normal -0.928977 -0.370138 0
52 | outer loop
53 | vertex 37.9061 -37.2279 37.5
54 | vertex 36.303 -33.2044 37.5
55 | vertex 36.303 -33.2044 -37.5
56 | endloop
57 | endfacet
58 | facet normal -0.883512 -0.468408 0
59 | outer loop
60 | vertex 36.303 -33.2044 -37.5
61 | vertex 36.303 -33.2044 37.5
62 | vertex 34.2743 -29.3778 -37.5
63 | endloop
64 | endfacet
65 | facet normal -0.883512 -0.468408 0
66 | outer loop
67 | vertex 36.303 -33.2044 37.5
68 | vertex 34.2743 -29.3778 37.5
69 | vertex 34.2743 -29.3778 -37.5
70 | endloop
71 | endfacet
72 | facet normal -0.827689 -0.561187 0
73 | outer loop
74 | vertex 34.2743 -29.3778 -37.5
75 | vertex 34.2743 -29.3778 37.5
76 | vertex 31.8437 -25.793 -37.5
77 | endloop
78 | endfacet
79 | facet normal -0.827689 -0.561187 0
80 | outer loop
81 | vertex 34.2743 -29.3778 37.5
82 | vertex 31.8437 -25.793 37.5
83 | vertex 31.8437 -25.793 -37.5
84 | endloop
85 | endfacet
86 | facet normal -0.762162 -0.647386 0
87 | outer loop
88 | vertex 31.8437 -25.793 -37.5
89 | vertex 31.8437 -25.793 37.5
90 | vertex 29.0398 -22.492 -37.5
91 | endloop
92 | endfacet
93 | facet normal -0.762162 -0.647386 0
94 | outer loop
95 | vertex 31.8437 -25.793 37.5
96 | vertex 29.0398 -22.492 37.5
97 | vertex 29.0398 -22.492 -37.5
98 | endloop
99 | endfacet
100 | facet normal -0.6877 -0.725995 0
101 | outer loop
102 | vertex 29.0398 -22.492 -37.5
103 | vertex 29.0398 -22.492 37.5
104 | vertex 25.8955 -19.5135 -37.5
105 | endloop
106 | endfacet
107 | facet normal -0.6877 -0.725995 0
108 | outer loop
109 | vertex 29.0398 -22.492 37.5
110 | vertex 25.8955 -19.5135 37.5
111 | vertex 25.8955 -19.5135 -37.5
112 | endloop
113 | endfacet
114 | facet normal -0.605174 -0.796093 0
115 | outer loop
116 | vertex 25.8955 -19.5135 -37.5
117 | vertex 25.8955 -19.5135 37.5
118 | vertex 22.4475 -16.8924 -37.5
119 | endloop
120 | endfacet
121 | facet normal -0.605174 -0.796093 0
122 | outer loop
123 | vertex 25.8955 -19.5135 37.5
124 | vertex 22.4475 -16.8924 37.5
125 | vertex 22.4475 -16.8924 -37.5
126 | endloop
127 | endfacet
128 | facet normal -0.515554 -0.856857 0
129 | outer loop
130 | vertex 22.4475 -16.8924 -37.5
131 | vertex 22.4475 -16.8924 37.5
132 | vertex 18.7363 -14.6595 -37.5
133 | endloop
134 | endfacet
135 | facet normal -0.515554 -0.856857 0
136 | outer loop
137 | vertex 22.4475 -16.8924 37.5
138 | vertex 18.7363 -14.6595 37.5
139 | vertex 18.7363 -14.6595 -37.5
140 | endloop
141 | endfacet
142 | facet normal -0.419889 -0.907575 0
143 | outer loop
144 | vertex 18.7363 -14.6595 -37.5
145 | vertex 18.7363 -14.6595 37.5
146 | vertex 14.8055 -12.8409 -37.5
147 | endloop
148 | endfacet
149 | facet normal -0.419889 -0.907575 0
150 | outer loop
151 | vertex 18.7363 -14.6595 37.5
152 | vertex 14.8055 -12.8409 37.5
153 | vertex 14.8055 -12.8409 -37.5
154 | endloop
155 | endfacet
156 | facet normal -0.319302 -0.947653 0
157 | outer loop
158 | vertex 14.8055 -12.8409 -37.5
159 | vertex 14.8055 -12.8409 37.5
160 | vertex 10.7011 -11.458 -37.5
161 | endloop
162 | endfacet
163 | facet normal -0.319302 -0.947653 0
164 | outer loop
165 | vertex 14.8055 -12.8409 37.5
166 | vertex 10.7011 -11.458 37.5
167 | vertex 10.7011 -11.458 -37.5
168 | endloop
169 | endfacet
170 | facet normal -0.21497 -0.976621 0
171 | outer loop
172 | vertex 10.7011 -11.458 -37.5
173 | vertex 10.7011 -11.458 37.5
174 | vertex 6.47128 -10.5269 -37.5
175 | endloop
176 | endfacet
177 | facet normal -0.21497 -0.976621 0
178 | outer loop
179 | vertex 10.7011 -11.458 37.5
180 | vertex 6.47128 -10.5269 37.5
181 | vertex 6.47128 -10.5269 -37.5
182 | endloop
183 | endfacet
184 | facet normal -0.108119 -0.994138 0
185 | outer loop
186 | vertex 6.47128 -10.5269 -37.5
187 | vertex 6.47128 -10.5269 37.5
188 | vertex 2.16556 -10.0587 -37.5
189 | endloop
190 | endfacet
191 | facet normal -0.108119 -0.994138 0
192 | outer loop
193 | vertex 6.47128 -10.5269 37.5
194 | vertex 2.16556 -10.0587 37.5
195 | vertex 2.16556 -10.0587 -37.5
196 | endloop
197 | endfacet
198 | facet normal 0 -1 0
199 | outer loop
200 | vertex 2.16556 -10.0587 -37.5
201 | vertex 2.16556 -10.0587 37.5
202 | vertex -2.16556 -10.0587 -37.5
203 | endloop
204 | endfacet
205 | facet normal 0 -1 0
206 | outer loop
207 | vertex 2.16556 -10.0587 37.5
208 | vertex -2.16556 -10.0587 37.5
209 | vertex -2.16556 -10.0587 -37.5
210 | endloop
211 | endfacet
212 | facet normal 0.108119 -0.994138 0
213 | outer loop
214 | vertex -2.16556 -10.0587 -37.5
215 | vertex -2.16556 -10.0587 37.5
216 | vertex -6.47128 -10.5269 -37.5
217 | endloop
218 | endfacet
219 | facet normal 0.108119 -0.994138 0
220 | outer loop
221 | vertex -2.16556 -10.0587 37.5
222 | vertex -6.47128 -10.5269 37.5
223 | vertex -6.47128 -10.5269 -37.5
224 | endloop
225 | endfacet
226 | facet normal 0.21497 -0.976621 0
227 | outer loop
228 | vertex -6.47128 -10.5269 -37.5
229 | vertex -6.47128 -10.5269 37.5
230 | vertex -10.7011 -11.458 -37.5
231 | endloop
232 | endfacet
233 | facet normal 0.21497 -0.976621 0
234 | outer loop
235 | vertex -6.47128 -10.5269 37.5
236 | vertex -10.7011 -11.458 37.5
237 | vertex -10.7011 -11.458 -37.5
238 | endloop
239 | endfacet
240 | facet normal 0.319302 -0.947653 0
241 | outer loop
242 | vertex -10.7011 -11.458 -37.5
243 | vertex -10.7011 -11.458 37.5
244 | vertex -14.8055 -12.8409 -37.5
245 | endloop
246 | endfacet
247 | facet normal 0.319302 -0.947653 0
248 | outer loop
249 | vertex -10.7011 -11.458 37.5
250 | vertex -14.8055 -12.8409 37.5
251 | vertex -14.8055 -12.8409 -37.5
252 | endloop
253 | endfacet
254 | facet normal 0.419889 -0.907575 0
255 | outer loop
256 | vertex -14.8055 -12.8409 -37.5
257 | vertex -14.8055 -12.8409 37.5
258 | vertex -18.7363 -14.6595 -37.5
259 | endloop
260 | endfacet
261 | facet normal 0.419889 -0.907575 0
262 | outer loop
263 | vertex -14.8055 -12.8409 37.5
264 | vertex -18.7363 -14.6595 37.5
265 | vertex -18.7363 -14.6595 -37.5
266 | endloop
267 | endfacet
268 | facet normal 0.515554 -0.856857 0
269 | outer loop
270 | vertex -18.7363 -14.6595 -37.5
271 | vertex -18.7363 -14.6595 37.5
272 | vertex -22.4475 -16.8924 -37.5
273 | endloop
274 | endfacet
275 | facet normal 0.515554 -0.856857 0
276 | outer loop
277 | vertex -18.7363 -14.6595 37.5
278 | vertex -22.4475 -16.8924 37.5
279 | vertex -22.4475 -16.8924 -37.5
280 | endloop
281 | endfacet
282 | facet normal 0.605174 -0.796093 0
283 | outer loop
284 | vertex -22.4475 -16.8924 -37.5
285 | vertex -22.4475 -16.8924 37.5
286 | vertex -25.8955 -19.5135 -37.5
287 | endloop
288 | endfacet
289 | facet normal 0.605174 -0.796093 0
290 | outer loop
291 | vertex -22.4475 -16.8924 37.5
292 | vertex -25.8955 -19.5135 37.5
293 | vertex -25.8955 -19.5135 -37.5
294 | endloop
295 | endfacet
296 | facet normal 0.6877 -0.725995 0
297 | outer loop
298 | vertex -25.8955 -19.5135 -37.5
299 | vertex -25.8955 -19.5135 37.5
300 | vertex -29.0398 -22.492 -37.5
301 | endloop
302 | endfacet
303 | facet normal 0.6877 -0.725995 0
304 | outer loop
305 | vertex -25.8955 -19.5135 37.5
306 | vertex -29.0398 -22.492 37.5
307 | vertex -29.0398 -22.492 -37.5
308 | endloop
309 | endfacet
310 | facet normal 0.762162 -0.647386 0
311 | outer loop
312 | vertex -29.0398 -22.492 -37.5
313 | vertex -29.0398 -22.492 37.5
314 | vertex -31.8437 -25.793 -37.5
315 | endloop
316 | endfacet
317 | facet normal 0.762162 -0.647386 0
318 | outer loop
319 | vertex -29.0398 -22.492 37.5
320 | vertex -31.8437 -25.793 37.5
321 | vertex -31.8437 -25.793 -37.5
322 | endloop
323 | endfacet
324 | facet normal 0.827689 -0.561187 0
325 | outer loop
326 | vertex -31.8437 -25.793 -37.5
327 | vertex -31.8437 -25.793 37.5
328 | vertex -34.2743 -29.3778 -37.5
329 | endloop
330 | endfacet
331 | facet normal 0.827689 -0.561187 0
332 | outer loop
333 | vertex -31.8437 -25.793 37.5
334 | vertex -34.2743 -29.3778 37.5
335 | vertex -34.2743 -29.3778 -37.5
336 | endloop
337 | endfacet
338 | facet normal 0.883512 -0.468408 0
339 | outer loop
340 | vertex -34.2743 -29.3778 -37.5
341 | vertex -34.2743 -29.3778 37.5
342 | vertex -36.303 -33.2044 -37.5
343 | endloop
344 | endfacet
345 | facet normal 0.883512 -0.468408 0
346 | outer loop
347 | vertex -34.2743 -29.3778 37.5
348 | vertex -36.303 -33.2044 37.5
349 | vertex -36.303 -33.2044 -37.5
350 | endloop
351 | endfacet
352 | facet normal 0.928977 -0.370138 0
353 | outer loop
354 | vertex -36.303 -33.2044 -37.5
355 | vertex -36.303 -33.2044 37.5
356 | vertex -37.9061 -37.2279 -37.5
357 | endloop
358 | endfacet
359 | facet normal 0.928977 -0.370138 0
360 | outer loop
361 | vertex -36.303 -33.2044 37.5
362 | vertex -37.9061 -37.2279 37.5
363 | vertex -37.9061 -37.2279 -37.5
364 | endloop
365 | endfacet
366 | facet normal 0.96355 -0.267529 0
367 | outer loop
368 | vertex -37.9061 -37.2279 -37.5
369 | vertex -37.9061 -37.2279 37.5
370 | vertex -39.0648 -41.4012 -37.5
371 | endloop
372 | endfacet
373 | facet normal 0.96355 -0.267529 0
374 | outer loop
375 | vertex -37.9061 -37.2279 37.5
376 | vertex -39.0648 -41.4012 37.5
377 | vertex -39.0648 -41.4012 -37.5
378 | endloop
379 | endfacet
380 | facet normal 0.986826 -0.161782 0
381 | outer loop
382 | vertex -39.0648 -41.4012 -37.5
383 | vertex -39.0648 -41.4012 37.5
384 | vertex -39.7655 -45.6752 -37.5
385 | endloop
386 | endfacet
387 | facet normal 0.986826 -0.161782 0
388 | outer loop
389 | vertex -39.0648 -41.4012 37.5
390 | vertex -39.7655 -45.6752 37.5
391 | vertex -39.7655 -45.6752 -37.5
392 | endloop
393 | endfacet
394 | facet normal 0.998533 -0.0541387 0
395 | outer loop
396 | vertex -39.7655 -45.6752 -37.5
397 | vertex -39.7655 -45.6752 37.5
398 | vertex -40 -50 -37.5
399 | endloop
400 | endfacet
401 | facet normal 0.998533 -0.0541387 0
402 | outer loop
403 | vertex -39.7655 -45.6752 37.5
404 | vertex -40 -50 37.5
405 | vertex -40 -50 -37.5
406 | endloop
407 | endfacet
408 | facet normal 0.998533 0.0541387 0
409 | outer loop
410 | vertex -40 -50 -37.5
411 | vertex -40 -50 37.5
412 | vertex -39.7655 -54.3248 -37.5
413 | endloop
414 | endfacet
415 | facet normal 0.998533 0.0541387 0
416 | outer loop
417 | vertex -40 -50 37.5
418 | vertex -39.7655 -54.3248 37.5
419 | vertex -39.7655 -54.3248 -37.5
420 | endloop
421 | endfacet
422 | facet normal 0.986826 0.161782 0
423 | outer loop
424 | vertex -39.7655 -54.3248 -37.5
425 | vertex -39.7655 -54.3248 37.5
426 | vertex -39.0648 -58.5988 -37.5
427 | endloop
428 | endfacet
429 | facet normal 0.986826 0.161782 0
430 | outer loop
431 | vertex -39.7655 -54.3248 37.5
432 | vertex -39.0648 -58.5988 37.5
433 | vertex -39.0648 -58.5988 -37.5
434 | endloop
435 | endfacet
436 | facet normal 0.96355 0.267529 0
437 | outer loop
438 | vertex -39.0648 -58.5988 -37.5
439 | vertex -39.0648 -58.5988 37.5
440 | vertex -37.9061 -62.7721 -37.5
441 | endloop
442 | endfacet
443 | facet normal 0.96355 0.267529 0
444 | outer loop
445 | vertex -39.0648 -58.5988 37.5
446 | vertex -37.9061 -62.7721 37.5
447 | vertex -37.9061 -62.7721 -37.5
448 | endloop
449 | endfacet
450 | facet normal 0.928977 0.370137 0
451 | outer loop
452 | vertex -37.9061 -62.7721 -37.5
453 | vertex -37.9061 -62.7721 37.5
454 | vertex -36.303 -66.7956 -37.5
455 | endloop
456 | endfacet
457 | facet normal 0.928977 0.370137 0
458 | outer loop
459 | vertex -37.9061 -62.7721 37.5
460 | vertex -36.303 -66.7956 37.5
461 | vertex -36.303 -66.7956 -37.5
462 | endloop
463 | endfacet
464 | facet normal 0.883512 0.468409 0
465 | outer loop
466 | vertex -36.303 -66.7956 -37.5
467 | vertex -36.303 -66.7956 37.5
468 | vertex -34.2743 -70.6222 -37.5
469 | endloop
470 | endfacet
471 | facet normal 0.883512 0.468409 0
472 | outer loop
473 | vertex -36.303 -66.7956 37.5
474 | vertex -34.2743 -70.6222 37.5
475 | vertex -34.2743 -70.6222 -37.5
476 | endloop
477 | endfacet
478 | facet normal 0.827689 0.561187 0
479 | outer loop
480 | vertex -34.2743 -70.6222 -37.5
481 | vertex -34.2743 -70.6222 37.5
482 | vertex -31.8437 -74.207 -37.5
483 | endloop
484 | endfacet
485 | facet normal 0.827689 0.561187 0
486 | outer loop
487 | vertex -34.2743 -70.6222 37.5
488 | vertex -31.8437 -74.207 37.5
489 | vertex -31.8437 -74.207 -37.5
490 | endloop
491 | endfacet
492 | facet normal 0.762162 0.647386 0
493 | outer loop
494 | vertex -31.8437 -74.207 -37.5
495 | vertex -31.8437 -74.207 37.5
496 | vertex -29.0398 -77.508 -37.5
497 | endloop
498 | endfacet
499 | facet normal 0.762162 0.647386 0
500 | outer loop
501 | vertex -31.8437 -74.207 37.5
502 | vertex -29.0398 -77.508 37.5
503 | vertex -29.0398 -77.508 -37.5
504 | endloop
505 | endfacet
506 | facet normal 0.6877 0.725995 0
507 | outer loop
508 | vertex -29.0398 -77.508 -37.5
509 | vertex -29.0398 -77.508 37.5
510 | vertex -25.8955 -80.4865 -37.5
511 | endloop
512 | endfacet
513 | facet normal 0.6877 0.725995 0
514 | outer loop
515 | vertex -29.0398 -77.508 37.5
516 | vertex -25.8955 -80.4865 37.5
517 | vertex -25.8955 -80.4865 -37.5
518 | endloop
519 | endfacet
520 | facet normal 0.605174 0.796093 0
521 | outer loop
522 | vertex -25.8955 -80.4865 -37.5
523 | vertex -25.8955 -80.4865 37.5
524 | vertex -22.4475 -83.1076 -37.5
525 | endloop
526 | endfacet
527 | facet normal 0.605174 0.796093 0
528 | outer loop
529 | vertex -25.8955 -80.4865 37.5
530 | vertex -22.4475 -83.1076 37.5
531 | vertex -22.4475 -83.1076 -37.5
532 | endloop
533 | endfacet
534 | facet normal 0.515554 0.856857 0
535 | outer loop
536 | vertex -22.4475 -83.1076 -37.5
537 | vertex -22.4475 -83.1076 37.5
538 | vertex -18.7363 -85.3405 -37.5
539 | endloop
540 | endfacet
541 | facet normal 0.515554 0.856857 0
542 | outer loop
543 | vertex -22.4475 -83.1076 37.5
544 | vertex -18.7363 -85.3405 37.5
545 | vertex -18.7363 -85.3405 -37.5
546 | endloop
547 | endfacet
548 | facet normal 0.419888 0.907576 0
549 | outer loop
550 | vertex -18.7363 -85.3405 -37.5
551 | vertex -18.7363 -85.3405 37.5
552 | vertex -14.8055 -87.1591 -37.5
553 | endloop
554 | endfacet
555 | facet normal 0.419888 0.907576 0
556 | outer loop
557 | vertex -18.7363 -85.3405 37.5
558 | vertex -14.8055 -87.1591 37.5
559 | vertex -14.8055 -87.1591 -37.5
560 | endloop
561 | endfacet
562 | facet normal 0.319302 0.947653 0
563 | outer loop
564 | vertex -14.8055 -87.1591 -37.5
565 | vertex -14.8055 -87.1591 37.5
566 | vertex -10.7011 -88.542 -37.5
567 | endloop
568 | endfacet
569 | facet normal 0.319302 0.947653 0
570 | outer loop
571 | vertex -14.8055 -87.1591 37.5
572 | vertex -10.7011 -88.542 37.5
573 | vertex -10.7011 -88.542 -37.5
574 | endloop
575 | endfacet
576 | facet normal 0.214971 0.976621 0
577 | outer loop
578 | vertex -10.7011 -88.542 -37.5
579 | vertex -10.7011 -88.542 37.5
580 | vertex -6.47128 -89.4731 -37.5
581 | endloop
582 | endfacet
583 | facet normal 0.214971 0.976621 0
584 | outer loop
585 | vertex -10.7011 -88.542 37.5
586 | vertex -6.47128 -89.4731 37.5
587 | vertex -6.47128 -89.4731 -37.5
588 | endloop
589 | endfacet
590 | facet normal 0.108119 0.994138 0
591 | outer loop
592 | vertex -6.47128 -89.4731 -37.5
593 | vertex -6.47128 -89.4731 37.5
594 | vertex -2.16556 -89.9413 -37.5
595 | endloop
596 | endfacet
597 | facet normal 0.108119 0.994138 0
598 | outer loop
599 | vertex -6.47128 -89.4731 37.5
600 | vertex -2.16556 -89.9413 37.5
601 | vertex -2.16556 -89.9413 -37.5
602 | endloop
603 | endfacet
604 | facet normal 0 1 0
605 | outer loop
606 | vertex -2.16556 -89.9413 -37.5
607 | vertex -2.16556 -89.9413 37.5
608 | vertex 2.16556 -89.9413 -37.5
609 | endloop
610 | endfacet
611 | facet normal 0 1 0
612 | outer loop
613 | vertex -2.16556 -89.9413 37.5
614 | vertex 2.16556 -89.9413 37.5
615 | vertex 2.16556 -89.9413 -37.5
616 | endloop
617 | endfacet
618 | facet normal -0.108119 0.994138 0
619 | outer loop
620 | vertex 2.16556 -89.9413 -37.5
621 | vertex 2.16556 -89.9413 37.5
622 | vertex 6.47128 -89.4731 -37.5
623 | endloop
624 | endfacet
625 | facet normal -0.108119 0.994138 0
626 | outer loop
627 | vertex 2.16556 -89.9413 37.5
628 | vertex 6.47128 -89.4731 37.5
629 | vertex 6.47128 -89.4731 -37.5
630 | endloop
631 | endfacet
632 | facet normal -0.214971 0.976621 0
633 | outer loop
634 | vertex 6.47128 -89.4731 -37.5
635 | vertex 6.47128 -89.4731 37.5
636 | vertex 10.7011 -88.542 -37.5
637 | endloop
638 | endfacet
639 | facet normal -0.214971 0.976621 0
640 | outer loop
641 | vertex 6.47128 -89.4731 37.5
642 | vertex 10.7011 -88.542 37.5
643 | vertex 10.7011 -88.542 -37.5
644 | endloop
645 | endfacet
646 | facet normal -0.319302 0.947653 0
647 | outer loop
648 | vertex 10.7011 -88.542 -37.5
649 | vertex 10.7011 -88.542 37.5
650 | vertex 14.8055 -87.1591 -37.5
651 | endloop
652 | endfacet
653 | facet normal -0.319302 0.947653 0
654 | outer loop
655 | vertex 10.7011 -88.542 37.5
656 | vertex 14.8055 -87.1591 37.5
657 | vertex 14.8055 -87.1591 -37.5
658 | endloop
659 | endfacet
660 | facet normal -0.419888 0.907576 0
661 | outer loop
662 | vertex 14.8055 -87.1591 -37.5
663 | vertex 14.8055 -87.1591 37.5
664 | vertex 18.7363 -85.3405 -37.5
665 | endloop
666 | endfacet
667 | facet normal -0.419888 0.907576 0
668 | outer loop
669 | vertex 14.8055 -87.1591 37.5
670 | vertex 18.7363 -85.3405 37.5
671 | vertex 18.7363 -85.3405 -37.5
672 | endloop
673 | endfacet
674 | facet normal -0.515554 0.856857 0
675 | outer loop
676 | vertex 18.7363 -85.3405 -37.5
677 | vertex 18.7363 -85.3405 37.5
678 | vertex 22.4475 -83.1076 -37.5
679 | endloop
680 | endfacet
681 | facet normal -0.515554 0.856857 0
682 | outer loop
683 | vertex 18.7363 -85.3405 37.5
684 | vertex 22.4475 -83.1076 37.5
685 | vertex 22.4475 -83.1076 -37.5
686 | endloop
687 | endfacet
688 | facet normal -0.605174 0.796093 0
689 | outer loop
690 | vertex 22.4475 -83.1076 -37.5
691 | vertex 22.4475 -83.1076 37.5
692 | vertex 25.8955 -80.4865 -37.5
693 | endloop
694 | endfacet
695 | facet normal -0.605174 0.796093 0
696 | outer loop
697 | vertex 22.4475 -83.1076 37.5
698 | vertex 25.8955 -80.4865 37.5
699 | vertex 25.8955 -80.4865 -37.5
700 | endloop
701 | endfacet
702 | facet normal -0.6877 0.725995 0
703 | outer loop
704 | vertex 25.8955 -80.4865 -37.5
705 | vertex 25.8955 -80.4865 37.5
706 | vertex 29.0398 -77.508 -37.5
707 | endloop
708 | endfacet
709 | facet normal -0.6877 0.725995 0
710 | outer loop
711 | vertex 25.8955 -80.4865 37.5
712 | vertex 29.0398 -77.508 37.5
713 | vertex 29.0398 -77.508 -37.5
714 | endloop
715 | endfacet
716 | facet normal -0.762162 0.647386 0
717 | outer loop
718 | vertex 29.0398 -77.508 -37.5
719 | vertex 29.0398 -77.508 37.5
720 | vertex 31.8437 -74.207 -37.5
721 | endloop
722 | endfacet
723 | facet normal -0.762162 0.647386 0
724 | outer loop
725 | vertex 29.0398 -77.508 37.5
726 | vertex 31.8437 -74.207 37.5
727 | vertex 31.8437 -74.207 -37.5
728 | endloop
729 | endfacet
730 | facet normal -0.827689 0.561187 0
731 | outer loop
732 | vertex 31.8437 -74.207 -37.5
733 | vertex 31.8437 -74.207 37.5
734 | vertex 34.2743 -70.6222 -37.5
735 | endloop
736 | endfacet
737 | facet normal -0.827689 0.561187 0
738 | outer loop
739 | vertex 31.8437 -74.207 37.5
740 | vertex 34.2743 -70.6222 37.5
741 | vertex 34.2743 -70.6222 -37.5
742 | endloop
743 | endfacet
744 | facet normal -0.883512 0.468409 0
745 | outer loop
746 | vertex 34.2743 -70.6222 -37.5
747 | vertex 34.2743 -70.6222 37.5
748 | vertex 36.303 -66.7956 -37.5
749 | endloop
750 | endfacet
751 | facet normal -0.883512 0.468409 0
752 | outer loop
753 | vertex 34.2743 -70.6222 37.5
754 | vertex 36.303 -66.7956 37.5
755 | vertex 36.303 -66.7956 -37.5
756 | endloop
757 | endfacet
758 | facet normal -0.928977 0.370137 0
759 | outer loop
760 | vertex 36.303 -66.7956 -37.5
761 | vertex 36.303 -66.7956 37.5
762 | vertex 37.9061 -62.7721 -37.5
763 | endloop
764 | endfacet
765 | facet normal -0.928977 0.370137 0
766 | outer loop
767 | vertex 36.303 -66.7956 37.5
768 | vertex 37.9061 -62.7721 37.5
769 | vertex 37.9061 -62.7721 -37.5
770 | endloop
771 | endfacet
772 | facet normal -0.96355 0.267529 0
773 | outer loop
774 | vertex 37.9061 -62.7721 -37.5
775 | vertex 37.9061 -62.7721 37.5
776 | vertex 39.0648 -58.5988 -37.5
777 | endloop
778 | endfacet
779 | facet normal -0.96355 0.267529 0
780 | outer loop
781 | vertex 37.9061 -62.7721 37.5
782 | vertex 39.0648 -58.5988 37.5
783 | vertex 39.0648 -58.5988 -37.5
784 | endloop
785 | endfacet
786 | facet normal -0.986826 0.161782 0
787 | outer loop
788 | vertex 39.0648 -58.5988 -37.5
789 | vertex 39.0648 -58.5988 37.5
790 | vertex 39.7655 -54.3248 -37.5
791 | endloop
792 | endfacet
793 | facet normal -0.986826 0.161782 0
794 | outer loop
795 | vertex 39.0648 -58.5988 37.5
796 | vertex 39.7655 -54.3248 37.5
797 | vertex 39.7655 -54.3248 -37.5
798 | endloop
799 | endfacet
800 | facet normal -0.998533 0.0541387 0
801 | outer loop
802 | vertex 39.7655 -54.3248 -37.5
803 | vertex 39.7655 -54.3248 37.5
804 | vertex 40 -50 -37.5
805 | endloop
806 | endfacet
807 | facet normal -0.998533 0.0541387 0
808 | outer loop
809 | vertex 39.7655 -54.3248 37.5
810 | vertex 40 -50 37.5
811 | vertex 40 -50 -37.5
812 | endloop
813 | endfacet
814 | facet normal 1 0 0
815 | outer loop
816 | vertex 75 -102.5 37.5
817 | vertex 75 -102.5 -37.5
818 | vertex 75 0 37.5
819 | endloop
820 | endfacet
821 | facet normal 1 0 0
822 | outer loop
823 | vertex 75 -102.5 -37.5
824 | vertex 75 0 -37.5
825 | vertex 75 0 37.5
826 | endloop
827 | endfacet
828 | facet normal 0 0 1
829 | outer loop
830 | vertex 22.4475 -83.1076 37.5
831 | vertex 18.7363 -85.3405 37.5
832 | vertex 75 -102.5 37.5
833 | endloop
834 | endfacet
835 | facet normal 0 0 1
836 | outer loop
837 | vertex 18.7363 -85.3405 37.5
838 | vertex 14.8055 -87.1591 37.5
839 | vertex 75 -102.5 37.5
840 | endloop
841 | endfacet
842 | facet normal 0 0 1
843 | outer loop
844 | vertex 75 -102.5 37.5
845 | vertex 14.8055 -87.1591 37.5
846 | vertex 10.7011 -88.542 37.5
847 | endloop
848 | endfacet
849 | facet normal 0 0 1
850 | outer loop
851 | vertex -14.8055 -12.8409 37.5
852 | vertex -10.7011 -11.458 37.5
853 | vertex -75 0 37.5
854 | endloop
855 | endfacet
856 | facet normal 0 0 1
857 | outer loop
858 | vertex 75 -102.5 37.5
859 | vertex 39.0648 -58.5988 37.5
860 | vertex 37.9061 -62.7721 37.5
861 | endloop
862 | endfacet
863 | facet normal 0 0 1
864 | outer loop
865 | vertex 37.9061 -62.7721 37.5
866 | vertex 36.303 -66.7956 37.5
867 | vertex 75 -102.5 37.5
868 | endloop
869 | endfacet
870 | facet normal 0 0 1
871 | outer loop
872 | vertex 36.303 -66.7956 37.5
873 | vertex 34.2743 -70.6222 37.5
874 | vertex 75 -102.5 37.5
875 | endloop
876 | endfacet
877 | facet normal 0 0 1
878 | outer loop
879 | vertex 75 -102.5 37.5
880 | vertex 34.2743 -70.6222 37.5
881 | vertex 31.8437 -74.207 37.5
882 | endloop
883 | endfacet
884 | facet normal 0 0 1
885 | outer loop
886 | vertex 31.8437 -74.207 37.5
887 | vertex 29.0398 -77.508 37.5
888 | vertex 75 -102.5 37.5
889 | endloop
890 | endfacet
891 | facet normal 0 0 1
892 | outer loop
893 | vertex 29.0398 -77.508 37.5
894 | vertex 25.8955 -80.4865 37.5
895 | vertex 75 -102.5 37.5
896 | endloop
897 | endfacet
898 | facet normal 0 0 1
899 | outer loop
900 | vertex 75 -102.5 37.5
901 | vertex 25.8955 -80.4865 37.5
902 | vertex 22.4475 -83.1076 37.5
903 | endloop
904 | endfacet
905 | facet normal 0 0 1
906 | outer loop
907 | vertex -6.47128 -89.4731 37.5
908 | vertex -10.7011 -88.542 37.5
909 | vertex -75 -102.5 37.5
910 | endloop
911 | endfacet
912 | facet normal 0 0 1
913 | outer loop
914 | vertex -10.7011 -88.542 37.5
915 | vertex -14.8055 -87.1591 37.5
916 | vertex -75 -102.5 37.5
917 | endloop
918 | endfacet
919 | facet normal 0 0 1
920 | outer loop
921 | vertex -14.8055 -87.1591 37.5
922 | vertex -18.7363 -85.3405 37.5
923 | vertex -75 -102.5 37.5
924 | endloop
925 | endfacet
926 | facet normal 0 0 1
927 | outer loop
928 | vertex -75 -102.5 37.5
929 | vertex -18.7363 -85.3405 37.5
930 | vertex -22.4475 -83.1076 37.5
931 | endloop
932 | endfacet
933 | facet normal 0 0 1
934 | outer loop
935 | vertex -29.0398 -22.492 37.5
936 | vertex -25.8955 -19.5135 37.5
937 | vertex -75 0 37.5
938 | endloop
939 | endfacet
940 | facet normal 0 0 1
941 | outer loop
942 | vertex 6.47128 -10.5269 37.5
943 | vertex 10.7011 -11.458 37.5
944 | vertex 75 0 37.5
945 | endloop
946 | endfacet
947 | facet normal 0 0 1
948 | outer loop
949 | vertex 10.7011 -11.458 37.5
950 | vertex 14.8055 -12.8409 37.5
951 | vertex 75 0 37.5
952 | endloop
953 | endfacet
954 | facet normal 0 0 1
955 | outer loop
956 | vertex 10.7011 -88.542 37.5
957 | vertex 6.47128 -89.4731 37.5
958 | vertex 75 -102.5 37.5
959 | endloop
960 | endfacet
961 | facet normal 0 0 1
962 | outer loop
963 | vertex 6.47128 -89.4731 37.5
964 | vertex 2.16556 -89.9413 37.5
965 | vertex 75 -102.5 37.5
966 | endloop
967 | endfacet
968 | facet normal 0 0 1
969 | outer loop
970 | vertex 75 -102.5 37.5
971 | vertex 2.16556 -89.9413 37.5
972 | vertex -75 -102.5 37.5
973 | endloop
974 | endfacet
975 | facet normal 0 0 1
976 | outer loop
977 | vertex 2.16556 -89.9413 37.5
978 | vertex -2.16556 -89.9413 37.5
979 | vertex -75 -102.5 37.5
980 | endloop
981 | endfacet
982 | facet normal 0 0 1
983 | outer loop
984 | vertex -75 -102.5 37.5
985 | vertex -2.16556 -89.9413 37.5
986 | vertex -6.47128 -89.4731 37.5
987 | endloop
988 | endfacet
989 | facet normal 0 0 1
990 | outer loop
991 | vertex -22.4475 -83.1076 37.5
992 | vertex -25.8955 -80.4865 37.5
993 | vertex -75 -102.5 37.5
994 | endloop
995 | endfacet
996 | facet normal 0 0 1
997 | outer loop
998 | vertex -25.8955 -80.4865 37.5
999 | vertex -29.0398 -77.508 37.5
1000 | vertex -75 -102.5 37.5
1001 | endloop
1002 | endfacet
1003 | facet normal 0 0 1
1004 | outer loop
1005 | vertex -75 -102.5 37.5
1006 | vertex -29.0398 -77.508 37.5
1007 | vertex -31.8437 -74.207 37.5
1008 | endloop
1009 | endfacet
1010 | facet normal 0 0 1
1011 | outer loop
1012 | vertex -25.8955 -19.5135 37.5
1013 | vertex -22.4475 -16.8924 37.5
1014 | vertex -75 0 37.5
1015 | endloop
1016 | endfacet
1017 | facet normal 0 0 1
1018 | outer loop
1019 | vertex -22.4475 -16.8924 37.5
1020 | vertex -18.7363 -14.6595 37.5
1021 | vertex -75 0 37.5
1022 | endloop
1023 | endfacet
1024 | facet normal 0 0 1
1025 | outer loop
1026 | vertex -75 0 37.5
1027 | vertex -18.7363 -14.6595 37.5
1028 | vertex -14.8055 -12.8409 37.5
1029 | endloop
1030 | endfacet
1031 | facet normal 0 0 1
1032 | outer loop
1033 | vertex 14.8055 -12.8409 37.5
1034 | vertex 18.7363 -14.6595 37.5
1035 | vertex 75 0 37.5
1036 | endloop
1037 | endfacet
1038 | facet normal 0 0 1
1039 | outer loop
1040 | vertex 18.7363 -14.6595 37.5
1041 | vertex 22.4475 -16.8924 37.5
1042 | vertex 75 0 37.5
1043 | endloop
1044 | endfacet
1045 | facet normal 0 0 1
1046 | outer loop
1047 | vertex 75 0 37.5
1048 | vertex 22.4475 -16.8924 37.5
1049 | vertex 25.8955 -19.5135 37.5
1050 | endloop
1051 | endfacet
1052 | facet normal 0 0 1
1053 | outer loop
1054 | vertex 25.8955 -19.5135 37.5
1055 | vertex 29.0398 -22.492 37.5
1056 | vertex 75 0 37.5
1057 | endloop
1058 | endfacet
1059 | facet normal 0 0 1
1060 | outer loop
1061 | vertex 29.0398 -22.492 37.5
1062 | vertex 31.8437 -25.793 37.5
1063 | vertex 75 0 37.5
1064 | endloop
1065 | endfacet
1066 | facet normal 0 0 1
1067 | outer loop
1068 | vertex 75 0 37.5
1069 | vertex 31.8437 -25.793 37.5
1070 | vertex 34.2743 -29.3778 37.5
1071 | endloop
1072 | endfacet
1073 | facet normal 0 0 1
1074 | outer loop
1075 | vertex -31.8437 -74.207 37.5
1076 | vertex -34.2743 -70.6222 37.5
1077 | vertex -75 -102.5 37.5
1078 | endloop
1079 | endfacet
1080 | facet normal 0 0 1
1081 | outer loop
1082 | vertex -34.2743 -70.6222 37.5
1083 | vertex -36.303 -66.7956 37.5
1084 | vertex -75 -102.5 37.5
1085 | endloop
1086 | endfacet
1087 | facet normal 0 0 1
1088 | outer loop
1089 | vertex -75 -102.5 37.5
1090 | vertex -36.303 -66.7956 37.5
1091 | vertex -37.9061 -62.7721 37.5
1092 | endloop
1093 | endfacet
1094 | facet normal 0 0 1
1095 | outer loop
1096 | vertex -39.7655 -45.6752 37.5
1097 | vertex -39.0648 -41.4012 37.5
1098 | vertex -75 0 37.5
1099 | endloop
1100 | endfacet
1101 | facet normal 0 0 1
1102 | outer loop
1103 | vertex -39.0648 -41.4012 37.5
1104 | vertex -37.9061 -37.2279 37.5
1105 | vertex -75 0 37.5
1106 | endloop
1107 | endfacet
1108 | facet normal 0 0 1
1109 | outer loop
1110 | vertex -75 0 37.5
1111 | vertex -37.9061 -37.2279 37.5
1112 | vertex -36.303 -33.2044 37.5
1113 | endloop
1114 | endfacet
1115 | facet normal 0 0 1
1116 | outer loop
1117 | vertex -10.7011 -11.458 37.5
1118 | vertex -6.47128 -10.5269 37.5
1119 | vertex -75 0 37.5
1120 | endloop
1121 | endfacet
1122 | facet normal 0 0 1
1123 | outer loop
1124 | vertex -6.47128 -10.5269 37.5
1125 | vertex -2.16556 -10.0587 37.5
1126 | vertex -75 0 37.5
1127 | endloop
1128 | endfacet
1129 | facet normal 0 0 1
1130 | outer loop
1131 | vertex -75 0 37.5
1132 | vertex -2.16556 -10.0587 37.5
1133 | vertex 75 0 37.5
1134 | endloop
1135 | endfacet
1136 | facet normal 0 0 1
1137 | outer loop
1138 | vertex -2.16556 -10.0587 37.5
1139 | vertex 2.16556 -10.0587 37.5
1140 | vertex 75 0 37.5
1141 | endloop
1142 | endfacet
1143 | facet normal 0 0 1
1144 | outer loop
1145 | vertex 75 0 37.5
1146 | vertex 2.16556 -10.0587 37.5
1147 | vertex 6.47128 -10.5269 37.5
1148 | endloop
1149 | endfacet
1150 | facet normal 0 0 1
1151 | outer loop
1152 | vertex 34.2743 -29.3778 37.5
1153 | vertex 36.303 -33.2044 37.5
1154 | vertex 75 0 37.5
1155 | endloop
1156 | endfacet
1157 | facet normal 0 0 1
1158 | outer loop
1159 | vertex 36.303 -33.2044 37.5
1160 | vertex 37.9061 -37.2279 37.5
1161 | vertex 75 0 37.5
1162 | endloop
1163 | endfacet
1164 | facet normal 0 0 1
1165 | outer loop
1166 | vertex 75 0 37.5
1167 | vertex 37.9061 -37.2279 37.5
1168 | vertex 39.0648 -41.4012 37.5
1169 | endloop
1170 | endfacet
1171 | facet normal 0 0 1
1172 | outer loop
1173 | vertex -37.9061 -62.7721 37.5
1174 | vertex -39.0648 -58.5988 37.5
1175 | vertex -75 -102.5 37.5
1176 | endloop
1177 | endfacet
1178 | facet normal 0 0 1
1179 | outer loop
1180 | vertex -39.0648 -58.5988 37.5
1181 | vertex -39.7655 -54.3248 37.5
1182 | vertex -75 -102.5 37.5
1183 | endloop
1184 | endfacet
1185 | facet normal 0 0 1
1186 | outer loop
1187 | vertex -75 -102.5 37.5
1188 | vertex -39.7655 -54.3248 37.5
1189 | vertex -75 0 37.5
1190 | endloop
1191 | endfacet
1192 | facet normal 0 0 1
1193 | outer loop
1194 | vertex -39.7655 -54.3248 37.5
1195 | vertex -40 -50 37.5
1196 | vertex -75 0 37.5
1197 | endloop
1198 | endfacet
1199 | facet normal 0 0 1
1200 | outer loop
1201 | vertex -75 0 37.5
1202 | vertex -40 -50 37.5
1203 | vertex -39.7655 -45.6752 37.5
1204 | endloop
1205 | endfacet
1206 | facet normal 0 0 1
1207 | outer loop
1208 | vertex -36.303 -33.2044 37.5
1209 | vertex -34.2743 -29.3778 37.5
1210 | vertex -75 0 37.5
1211 | endloop
1212 | endfacet
1213 | facet normal 0 0 1
1214 | outer loop
1215 | vertex -34.2743 -29.3778 37.5
1216 | vertex -31.8437 -25.793 37.5
1217 | vertex -75 0 37.5
1218 | endloop
1219 | endfacet
1220 | facet normal 0 0 1
1221 | outer loop
1222 | vertex -75 0 37.5
1223 | vertex -31.8437 -25.793 37.5
1224 | vertex -29.0398 -22.492 37.5
1225 | endloop
1226 | endfacet
1227 | facet normal 0 0 1
1228 | outer loop
1229 | vertex 39.0648 -41.4012 37.5
1230 | vertex 39.7655 -45.6752 37.5
1231 | vertex 75 0 37.5
1232 | endloop
1233 | endfacet
1234 | facet normal 0 0 1
1235 | outer loop
1236 | vertex 39.7655 -45.6752 37.5
1237 | vertex 40 -50 37.5
1238 | vertex 75 0 37.5
1239 | endloop
1240 | endfacet
1241 | facet normal 0 0 1
1242 | outer loop
1243 | vertex 75 0 37.5
1244 | vertex 40 -50 37.5
1245 | vertex 75 -102.5 37.5
1246 | endloop
1247 | endfacet
1248 | facet normal 0 0 1
1249 | outer loop
1250 | vertex 40 -50 37.5
1251 | vertex 39.7655 -54.3248 37.5
1252 | vertex 75 -102.5 37.5
1253 | endloop
1254 | endfacet
1255 | facet normal 0 0 1
1256 | outer loop
1257 | vertex 75 -102.5 37.5
1258 | vertex 39.7655 -54.3248 37.5
1259 | vertex 39.0648 -58.5988 37.5
1260 | endloop
1261 | endfacet
1262 | facet normal 0 0 -1
1263 | outer loop
1264 | vertex 6.47128 -89.4731 -37.5
1265 | vertex 10.7011 -88.542 -37.5
1266 | vertex 75 -102.5 -37.5
1267 | endloop
1268 | endfacet
1269 | facet normal 0 0 -1
1270 | outer loop
1271 | vertex -75 -102.5 -37.5
1272 | vertex -39.0648 -58.5988 -37.5
1273 | vertex -37.9061 -62.7721 -37.5
1274 | endloop
1275 | endfacet
1276 | facet normal 0 0 -1
1277 | outer loop
1278 | vertex -37.9061 -62.7721 -37.5
1279 | vertex -36.303 -66.7956 -37.5
1280 | vertex -75 -102.5 -37.5
1281 | endloop
1282 | endfacet
1283 | facet normal 0 0 -1
1284 | outer loop
1285 | vertex -36.303 -66.7956 -37.5
1286 | vertex -34.2743 -70.6222 -37.5
1287 | vertex -75 -102.5 -37.5
1288 | endloop
1289 | endfacet
1290 | facet normal 0 0 -1
1291 | outer loop
1292 | vertex -75 -102.5 -37.5
1293 | vertex -34.2743 -70.6222 -37.5
1294 | vertex -31.8437 -74.207 -37.5
1295 | endloop
1296 | endfacet
1297 | facet normal 0 0 -1
1298 | outer loop
1299 | vertex -31.8437 -74.207 -37.5
1300 | vertex -29.0398 -77.508 -37.5
1301 | vertex -75 -102.5 -37.5
1302 | endloop
1303 | endfacet
1304 | facet normal 0 0 -1
1305 | outer loop
1306 | vertex -29.0398 -77.508 -37.5
1307 | vertex -25.8955 -80.4865 -37.5
1308 | vertex -75 -102.5 -37.5
1309 | endloop
1310 | endfacet
1311 | facet normal 0 0 -1
1312 | outer loop
1313 | vertex -75 -102.5 -37.5
1314 | vertex -25.8955 -80.4865 -37.5
1315 | vertex -22.4475 -83.1076 -37.5
1316 | endloop
1317 | endfacet
1318 | facet normal 0 0 -1
1319 | outer loop
1320 | vertex -22.4475 -83.1076 -37.5
1321 | vertex -18.7363 -85.3405 -37.5
1322 | vertex -75 -102.5 -37.5
1323 | endloop
1324 | endfacet
1325 | facet normal 0 0 -1
1326 | outer loop
1327 | vertex -18.7363 -85.3405 -37.5
1328 | vertex -14.8055 -87.1591 -37.5
1329 | vertex -75 -102.5 -37.5
1330 | endloop
1331 | endfacet
1332 | facet normal 0 0 -1
1333 | outer loop
1334 | vertex -75 -102.5 -37.5
1335 | vertex -14.8055 -87.1591 -37.5
1336 | vertex -10.7011 -88.542 -37.5
1337 | endloop
1338 | endfacet
1339 | facet normal 0 0 -1
1340 | outer loop
1341 | vertex 14.8055 -12.8409 -37.5
1342 | vertex 10.7011 -11.458 -37.5
1343 | vertex 75 0 -37.5
1344 | endloop
1345 | endfacet
1346 | facet normal 0 0 -1
1347 | outer loop
1348 | vertex -10.7011 -88.542 -37.5
1349 | vertex -6.47128 -89.4731 -37.5
1350 | vertex -75 -102.5 -37.5
1351 | endloop
1352 | endfacet
1353 | facet normal 0 0 -1
1354 | outer loop
1355 | vertex -6.47128 -89.4731 -37.5
1356 | vertex -2.16556 -89.9413 -37.5
1357 | vertex -75 -102.5 -37.5
1358 | endloop
1359 | endfacet
1360 | facet normal 0 0 -1
1361 | outer loop
1362 | vertex -75 -102.5 -37.5
1363 | vertex -2.16556 -89.9413 -37.5
1364 | vertex 75 -102.5 -37.5
1365 | endloop
1366 | endfacet
1367 | facet normal 0 0 -1
1368 | outer loop
1369 | vertex -2.16556 -89.9413 -37.5
1370 | vertex 2.16556 -89.9413 -37.5
1371 | vertex 75 -102.5 -37.5
1372 | endloop
1373 | endfacet
1374 | facet normal 0 0 -1
1375 | outer loop
1376 | vertex 75 -102.5 -37.5
1377 | vertex 2.16556 -89.9413 -37.5
1378 | vertex 6.47128 -89.4731 -37.5
1379 | endloop
1380 | endfacet
1381 | facet normal 0 0 -1
1382 | outer loop
1383 | vertex 10.7011 -88.542 -37.5
1384 | vertex 14.8055 -87.1591 -37.5
1385 | vertex 75 -102.5 -37.5
1386 | endloop
1387 | endfacet
1388 | facet normal 0 0 -1
1389 | outer loop
1390 | vertex 14.8055 -87.1591 -37.5
1391 | vertex 18.7363 -85.3405 -37.5
1392 | vertex 75 -102.5 -37.5
1393 | endloop
1394 | endfacet
1395 | facet normal 0 0 -1
1396 | outer loop
1397 | vertex 75 -102.5 -37.5
1398 | vertex 18.7363 -85.3405 -37.5
1399 | vertex 22.4475 -83.1076 -37.5
1400 | endloop
1401 | endfacet
1402 | facet normal 0 0 -1
1403 | outer loop
1404 | vertex 25.8955 -19.5135 -37.5
1405 | vertex 75 0 -37.5
1406 | vertex 29.0398 -22.492 -37.5
1407 | endloop
1408 | endfacet
1409 | facet normal 0 0 -1
1410 | outer loop
1411 | vertex 75 0 -37.5
1412 | vertex 31.8437 -25.793 -37.5
1413 | vertex 29.0398 -22.492 -37.5
1414 | endloop
1415 | endfacet
1416 | facet normal 0 0 -1
1417 | outer loop
1418 | vertex 25.8955 -19.5135 -37.5
1419 | vertex 22.4475 -16.8924 -37.5
1420 | vertex 75 0 -37.5
1421 | endloop
1422 | endfacet
1423 | facet normal 0 0 -1
1424 | outer loop
1425 | vertex 22.4475 -16.8924 -37.5
1426 | vertex 18.7363 -14.6595 -37.5
1427 | vertex 75 0 -37.5
1428 | endloop
1429 | endfacet
1430 | facet normal 0 0 -1
1431 | outer loop
1432 | vertex 75 0 -37.5
1433 | vertex 18.7363 -14.6595 -37.5
1434 | vertex 14.8055 -12.8409 -37.5
1435 | endloop
1436 | endfacet
1437 | facet normal 0 0 -1
1438 | outer loop
1439 | vertex -6.47128 -10.5269 -37.5
1440 | vertex -10.7011 -11.458 -37.5
1441 | vertex -75 0 -37.5
1442 | endloop
1443 | endfacet
1444 | facet normal 0 0 -1
1445 | outer loop
1446 | vertex -10.7011 -11.458 -37.5
1447 | vertex -14.8055 -12.8409 -37.5
1448 | vertex -75 0 -37.5
1449 | endloop
1450 | endfacet
1451 | facet normal 0 0 -1
1452 | outer loop
1453 | vertex 22.4475 -83.1076 -37.5
1454 | vertex 25.8955 -80.4865 -37.5
1455 | vertex 75 -102.5 -37.5
1456 | endloop
1457 | endfacet
1458 | facet normal 0 0 -1
1459 | outer loop
1460 | vertex 25.8955 -80.4865 -37.5
1461 | vertex 29.0398 -77.508 -37.5
1462 | vertex 75 -102.5 -37.5
1463 | endloop
1464 | endfacet
1465 | facet normal 0 0 -1
1466 | outer loop
1467 | vertex 75 -102.5 -37.5
1468 | vertex 29.0398 -77.508 -37.5
1469 | vertex 31.8437 -74.207 -37.5
1470 | endloop
1471 | endfacet
1472 | facet normal 0 0 -1
1473 | outer loop
1474 | vertex 31.8437 -74.207 -37.5
1475 | vertex 34.2743 -70.6222 -37.5
1476 | vertex 75 -102.5 -37.5
1477 | endloop
1478 | endfacet
1479 | facet normal 0 0 -1
1480 | outer loop
1481 | vertex 34.2743 -70.6222 -37.5
1482 | vertex 36.303 -66.7956 -37.5
1483 | vertex 75 -102.5 -37.5
1484 | endloop
1485 | endfacet
1486 | facet normal 0 0 -1
1487 | outer loop
1488 | vertex 75 -102.5 -37.5
1489 | vertex 36.303 -66.7956 -37.5
1490 | vertex 37.9061 -62.7721 -37.5
1491 | endloop
1492 | endfacet
1493 | facet normal 0 0 -1
1494 | outer loop
1495 | vertex 40 -50 -37.5
1496 | vertex 75 0 -37.5
1497 | vertex 39.7655 -54.3248 -37.5
1498 | endloop
1499 | endfacet
1500 | facet normal 0 0 -1
1501 | outer loop
1502 | vertex 75 0 -37.5
1503 | vertex 75 -102.5 -37.5
1504 | vertex 39.7655 -54.3248 -37.5
1505 | endloop
1506 | endfacet
1507 | facet normal 0 0 -1
1508 | outer loop
1509 | vertex 39.7655 -54.3248 -37.5
1510 | vertex 75 -102.5 -37.5
1511 | vertex 39.0648 -58.5988 -37.5
1512 | endloop
1513 | endfacet
1514 | facet normal 0 0 -1
1515 | outer loop
1516 | vertex 75 -102.5 -37.5
1517 | vertex 37.9061 -62.7721 -37.5
1518 | vertex 39.0648 -58.5988 -37.5
1519 | endloop
1520 | endfacet
1521 | facet normal 0 0 -1
1522 | outer loop
1523 | vertex 10.7011 -11.458 -37.5
1524 | vertex 6.47128 -10.5269 -37.5
1525 | vertex 75 0 -37.5
1526 | endloop
1527 | endfacet
1528 | facet normal 0 0 -1
1529 | outer loop
1530 | vertex 6.47128 -10.5269 -37.5
1531 | vertex 2.16556 -10.0587 -37.5
1532 | vertex 75 0 -37.5
1533 | endloop
1534 | endfacet
1535 | facet normal 0 0 -1
1536 | outer loop
1537 | vertex 75 0 -37.5
1538 | vertex 2.16556 -10.0587 -37.5
1539 | vertex -75 0 -37.5
1540 | endloop
1541 | endfacet
1542 | facet normal 0 0 -1
1543 | outer loop
1544 | vertex 2.16556 -10.0587 -37.5
1545 | vertex -2.16556 -10.0587 -37.5
1546 | vertex -75 0 -37.5
1547 | endloop
1548 | endfacet
1549 | facet normal 0 0 -1
1550 | outer loop
1551 | vertex -75 0 -37.5
1552 | vertex -2.16556 -10.0587 -37.5
1553 | vertex -6.47128 -10.5269 -37.5
1554 | endloop
1555 | endfacet
1556 | facet normal 0 0 -1
1557 | outer loop
1558 | vertex -14.8055 -12.8409 -37.5
1559 | vertex -18.7363 -14.6595 -37.5
1560 | vertex -75 0 -37.5
1561 | endloop
1562 | endfacet
1563 | facet normal 0 0 -1
1564 | outer loop
1565 | vertex -18.7363 -14.6595 -37.5
1566 | vertex -22.4475 -16.8924 -37.5
1567 | vertex -75 0 -37.5
1568 | endloop
1569 | endfacet
1570 | facet normal 0 0 -1
1571 | outer loop
1572 | vertex -75 0 -37.5
1573 | vertex -22.4475 -16.8924 -37.5
1574 | vertex -25.8955 -19.5135 -37.5
1575 | endloop
1576 | endfacet
1577 | facet normal 0 0 -1
1578 | outer loop
1579 | vertex -25.8955 -19.5135 -37.5
1580 | vertex -29.0398 -22.492 -37.5
1581 | vertex -75 0 -37.5
1582 | endloop
1583 | endfacet
1584 | facet normal 0 0 -1
1585 | outer loop
1586 | vertex -29.0398 -22.492 -37.5
1587 | vertex -31.8437 -25.793 -37.5
1588 | vertex -75 0 -37.5
1589 | endloop
1590 | endfacet
1591 | facet normal 0 0 -1
1592 | outer loop
1593 | vertex -75 0 -37.5
1594 | vertex -31.8437 -25.793 -37.5
1595 | vertex -34.2743 -29.3778 -37.5
1596 | endloop
1597 | endfacet
1598 | facet normal 0 0 -1
1599 | outer loop
1600 | vertex -34.2743 -29.3778 -37.5
1601 | vertex -36.303 -33.2044 -37.5
1602 | vertex -75 0 -37.5
1603 | endloop
1604 | endfacet
1605 | facet normal 0 0 -1
1606 | outer loop
1607 | vertex -36.303 -33.2044 -37.5
1608 | vertex -37.9061 -37.2279 -37.5
1609 | vertex -75 0 -37.5
1610 | endloop
1611 | endfacet
1612 | facet normal 0 0 -1
1613 | outer loop
1614 | vertex -75 0 -37.5
1615 | vertex -37.9061 -37.2279 -37.5
1616 | vertex -39.0648 -41.4012 -37.5
1617 | endloop
1618 | endfacet
1619 | facet normal 0 0 -1
1620 | outer loop
1621 | vertex -39.0648 -41.4012 -37.5
1622 | vertex -39.7655 -45.6752 -37.5
1623 | vertex -75 0 -37.5
1624 | endloop
1625 | endfacet
1626 | facet normal 0 0 -1
1627 | outer loop
1628 | vertex -39.7655 -45.6752 -37.5
1629 | vertex -40 -50 -37.5
1630 | vertex -75 0 -37.5
1631 | endloop
1632 | endfacet
1633 | facet normal 0 0 -1
1634 | outer loop
1635 | vertex -75 0 -37.5
1636 | vertex -40 -50 -37.5
1637 | vertex -75 -102.5 -37.5
1638 | endloop
1639 | endfacet
1640 | facet normal 0 0 -1
1641 | outer loop
1642 | vertex -40 -50 -37.5
1643 | vertex -39.7655 -54.3248 -37.5
1644 | vertex -75 -102.5 -37.5
1645 | endloop
1646 | endfacet
1647 | facet normal 0 0 -1
1648 | outer loop
1649 | vertex -75 -102.5 -37.5
1650 | vertex -39.7655 -54.3248 -37.5
1651 | vertex -39.0648 -58.5988 -37.5
1652 | endloop
1653 | endfacet
1654 | facet normal 0 0 -1
1655 | outer loop
1656 | vertex 40 -50 -37.5
1657 | vertex 39.7655 -45.6752 -37.5
1658 | vertex 75 0 -37.5
1659 | endloop
1660 | endfacet
1661 | facet normal 0 0 -1
1662 | outer loop
1663 | vertex 39.7655 -45.6752 -37.5
1664 | vertex 39.0648 -41.4012 -37.5
1665 | vertex 75 0 -37.5
1666 | endloop
1667 | endfacet
1668 | facet normal 0 0 -1
1669 | outer loop
1670 | vertex 75 0 -37.5
1671 | vertex 39.0648 -41.4012 -37.5
1672 | vertex 37.9061 -37.2279 -37.5
1673 | endloop
1674 | endfacet
1675 | facet normal 0 0 -1
1676 | outer loop
1677 | vertex 37.9061 -37.2279 -37.5
1678 | vertex 36.303 -33.2044 -37.5
1679 | vertex 75 0 -37.5
1680 | endloop
1681 | endfacet
1682 | facet normal 0 0 -1
1683 | outer loop
1684 | vertex 36.303 -33.2044 -37.5
1685 | vertex 34.2743 -29.3778 -37.5
1686 | vertex 75 0 -37.5
1687 | endloop
1688 | endfacet
1689 | facet normal 0 0 -1
1690 | outer loop
1691 | vertex 75 0 -37.5
1692 | vertex 34.2743 -29.3778 -37.5
1693 | vertex 31.8437 -25.793 -37.5
1694 | endloop
1695 | endfacet
1696 | facet normal -1 0 0
1697 | outer loop
1698 | vertex -75 -102.5 -37.5
1699 | vertex -75 -102.5 37.5
1700 | vertex -75 0 -37.5
1701 | endloop
1702 | endfacet
1703 | facet normal -1 0 0
1704 | outer loop
1705 | vertex -75 -102.5 37.5
1706 | vertex -75 0 37.5
1707 | vertex -75 0 -37.5
1708 | endloop
1709 | endfacet
1710 | facet normal 0 -1 0
1711 | outer loop
1712 | vertex 75 -102.5 37.5
1713 | vertex -75 -102.5 37.5
1714 | vertex 75 -102.5 -37.5
1715 | endloop
1716 | endfacet
1717 | facet normal 0 -1 0
1718 | outer loop
1719 | vertex -75 -102.5 37.5
1720 | vertex -75 -102.5 -37.5
1721 | vertex 75 -102.5 -37.5
1722 | endloop
1723 | endfacet
1724 | facet normal 0 1 0
1725 | outer loop
1726 | vertex -75 0 37.5
1727 | vertex 75 0 37.5
1728 | vertex -75 0 -37.5
1729 | endloop
1730 | endfacet
1731 | facet normal 0 1 0
1732 | outer loop
1733 | vertex 75 0 37.5
1734 | vertex 75 0 -37.5
1735 | vertex -75 0 -37.5
1736 | endloop
1737 | endfacet
1738 | endsolid Mesh
1739 |
--------------------------------------------------------------------------------
/cad/cnc-mill.scad:
--------------------------------------------------------------------------------
1 | use ;
2 |
3 |
4 | $fn = 60;
5 |
6 |
7 | // TASK: What is a good value for the minimum spindle height? I think to answer
8 | // this question, I think I need to answer the following ones first:
9 | // 1. Do I want to mill to be able to machine its own table?
10 | // 2. If the answer is no:
11 | // a) What is the thinnest piece of material that I might conceivably
12 | // machine?
13 | // a) what is the shortest tool I might conceivably use?
14 | translate([0, 0, 10])
15 | spindle();
16 |
17 | table(size_x = 300, size_y = 150, thickness = 10);
18 |
19 | module table(size_x, size_y, thickness) {
20 | translate([0, 0, -thickness / 2])
21 | cube([size_x, size_y, thickness], center = true);
22 | }
23 |
--------------------------------------------------------------------------------
/cad/spindle.scad:
--------------------------------------------------------------------------------
1 | // These are dimensions that are specified in the drawing.
2 | diameter = 80;
3 | diameter_collet = 19;
4 | diameter_neck_base = 29.5;
5 | diameter_shoulder = 54;
6 | height_collet = 13;
7 | height_collet_neck_neck_base = 36;
8 | height_neck_base = 3;
9 | height_shoulder = 18;
10 | height_body_bottom = 8;
11 | height_body_main = 164;
12 | height_body_top = 26;
13 | height_total = 261;
14 |
15 | // These dimensions are derived from the previous ones.
16 | height_neck = height_collet_neck_neck_base
17 | - height_collet
18 | - height_neck_base;
19 | height_connector = height_total
20 | - height_body_top - height_body_main - height_body_bottom
21 | - height_shoulder
22 | - height_collet_neck_neck_base;
23 |
24 | // These ones are just guesses, as the drawing doesn't say.
25 | diameter_neck = 15;
26 | diameter_connector = 20;
27 |
28 | // Colors
29 | black = [0.0, 0.0, 0.0, 1.0];
30 | silver = [0.8, 0.8, 0.8, 1.0];
31 |
32 | elements = [
33 | [ height_collet, diameter_collet, black], // collet
34 | [ height_neck, diameter_neck, silver], // neck
35 | [ height_neck_base, diameter_neck_base, silver], // neck base
36 | [ height_shoulder, diameter_shoulder, black], // shoulder
37 | [height_body_bottom, diameter, black], // body: bottom
38 | [ height_body_main, diameter, silver], // body: main
39 | [ height_body_top, diameter, black], // body: top
40 | [ height_connector, diameter_connector, silver], // connector
41 | ];
42 |
43 |
44 | // The CNC spindle motor
45 | //
46 | // https://www.zhonghuajiangspindle.com/1.5kw-cnc-air-cooled-spindle-motor-80mm.html
47 | module spindle() {
48 | union() {
49 | element(i = 0, elements = elements);
50 | }
51 |
52 | module element(i, elements) {
53 | if (i < len(elements)) {
54 | height = elements[i][0];
55 | diameter = elements[i][1];
56 | color = elements[i][2];
57 |
58 | color(color)
59 | cylinder(d = diameter, h = height);
60 |
61 | translate([0, 0, height])
62 | element(i = i + 1, elements = elements);
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/fj.toml:
--------------------------------------------------------------------------------
1 | default_model = "calculation"
2 |
--------------------------------------------------------------------------------
/model/.gitignore:
--------------------------------------------------------------------------------
1 | /Cargo.lock
2 | /target
3 |
--------------------------------------------------------------------------------
/model/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "model"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["cdylib"]
8 |
9 | [dependencies]
10 | fj = "0.16.0"
11 |
--------------------------------------------------------------------------------
/model/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Calculations for the CNC mill
2 | //!
3 | //! This is basically a Rust program, in the form of a [Fornjot] model, that
4 | //! does some calculations about the CNC mill, to help to select right-sized
5 | //! components.
6 | //!
7 | //! So, why is this a Fornjot model? Honestly, is doesn't make a whole lot of
8 | //! sense, if viewed in isolation. Fornjot doesn't have the features yet to
9 | //! model the CNC mill's geometry, and it has no simulation features at all. It
10 | //! doesn't really support anything I'm using it for here, and I'd probably be
11 | //! better off just doing it in Excel.
12 | //!
13 | //! However, I want to use Fornjot for modeling the CNC mill (or its successor)
14 | //! in the future, and it makes sense to do this kind of calculation together
15 | //! with the CAD model. I also want Fornjot to at least not stand in the way of
16 | //! use cases outside of its (current) core feature that, and using it for
17 | //! something it wasn't designed for should help there.
18 | //!
19 | //! Lastly, I usually only work on Fornjot itself, within its repository. Using
20 | //! it for something new outside of the repository was already hugely
21 | //! informative, in regards to some weaknesses it has, and where it might trip
22 | //! new users up.
23 | //!
24 | //! [Fornjot]: https://www.fornjot.app/
25 |
26 | mod machine;
27 | mod physics;
28 | mod tools;
29 |
30 | use std::fmt;
31 |
32 | use physics::Power;
33 |
34 | use crate::{
35 | machine::{axes, rails::mgn15_height_total, spindle::Spindle},
36 | physics::{Force, Radius},
37 | tools::Tool,
38 | };
39 |
40 | #[fj::model]
41 | fn cnc() -> fj::Shape {
42 | let spindle = Spindle::new(Power::from_value_kw(1.5));
43 | let tools = Tool::tools();
44 |
45 | let (worst_case_force, tool) = tools
46 | .into_iter()
47 | .map(|tool| {
48 | let (tangential_cutting_force, tool_torque) =
49 | tool.tangential_cutting_force();
50 |
51 | // Also figure out the torque that would require, and make sure it's
52 | // below the torque that the spindle can deliver.
53 | let spindle_torque = spindle.torque(tool.desired_rpm());
54 | if tool_torque > spindle_torque {
55 | println!(
56 | "Required torque ({tool_torque}) is larger than spindle \
57 | torque ({spindle_torque})!",
58 | );
59 | println!("Tool: {tool:#?}");
60 | println!(
61 | "Tangential cutting force: {tangential_cutting_force}"
62 | );
63 |
64 | return (
65 | TangentialCuttingForce::PerMaxSpindleTorque(
66 | spindle_torque.to_force(tool.diameter),
67 | ),
68 | tool,
69 | );
70 | }
71 |
72 | (
73 | TangentialCuttingForce::PerToolRequirements(
74 | tangential_cutting_force,
75 | ),
76 | tool,
77 | )
78 | })
79 | .reduce(|a, b| if a.0 > b.0 { a } else { b })
80 | .unwrap();
81 |
82 | println!("Worst-case tangential cutting force: {}", worst_case_force);
83 | println!("Tool: {tool:#?}");
84 |
85 | let y_axis_rail_max_distance_to_force =
86 | Radius::from_length(axes::y::table_thickness() + axes::z::max_travel())
87 | + Radius::from_length(mgn15_height_total() / 2.);
88 | let y_axis_rail_worst_case_torque = worst_case_force
89 | .value()
90 | .to_torque(y_axis_rail_max_distance_to_force);
91 | println!(
92 | "Worst-case torque at y-axis rail: {}",
93 | y_axis_rail_worst_case_torque
94 | );
95 |
96 | // This is a placeholder. We don't actually need to export geometry right
97 | // now, but Fornjot won't allow us to have a function that doesn't do that.
98 | let w = 0.5;
99 | fj::Sketch::from_points(vec![[-w, -w], [w, -w], [w, w], [-w, w]]).into()
100 | }
101 |
102 | #[derive(Clone, Copy, Debug)]
103 | pub enum TangentialCuttingForce {
104 | PerToolRequirements(Force),
105 | PerMaxSpindleTorque(Force),
106 | }
107 |
108 | impl TangentialCuttingForce {
109 | fn value(self) -> Force {
110 | match self {
111 | TangentialCuttingForce::PerToolRequirements(value) => value,
112 | TangentialCuttingForce::PerMaxSpindleTorque(value) => value,
113 | }
114 | }
115 | }
116 |
117 | impl PartialEq for TangentialCuttingForce {
118 | fn eq(&self, other: &Self) -> bool {
119 | self.value().eq(&other.value())
120 | }
121 | }
122 |
123 | impl PartialOrd for TangentialCuttingForce {
124 | fn partial_cmp(&self, other: &Self) -> Option {
125 | self.value().partial_cmp(&other.value())
126 | }
127 | }
128 |
129 | impl fmt::Display for TangentialCuttingForce {
130 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131 | write!(f, "{} ", self.value())?;
132 |
133 | match self {
134 | TangentialCuttingForce::PerToolRequirements(_) => {
135 | write!(f, "(per tool requirements)")?
136 | }
137 | TangentialCuttingForce::PerMaxSpindleTorque(_) => {
138 | write!(f, "(limited by max spindle torque)")?
139 | }
140 | }
141 |
142 | Ok(())
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/model/src/machine/axes/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod y;
2 | pub mod z;
3 |
--------------------------------------------------------------------------------
/model/src/machine/axes/y.rs:
--------------------------------------------------------------------------------
1 | use crate::physics::Length;
2 |
3 | pub fn table_thickness() -> Length {
4 | Length::from_value_mm(10.)
5 | }
6 |
--------------------------------------------------------------------------------
/model/src/machine/axes/z.rs:
--------------------------------------------------------------------------------
1 | use crate::physics::Length;
2 |
3 | /// The max travel of the z-axis
4 | ///
5 | /// This should be a constant, but it can't as floating-point arithmetic can not
6 | /// be `const`.
7 | pub fn max_travel() -> Length {
8 | Length::from_value_mm(100.)
9 | }
10 |
--------------------------------------------------------------------------------
/model/src/machine/mod.rs:
--------------------------------------------------------------------------------
1 | pub mod axes;
2 | pub mod rails;
3 | pub mod spindle;
4 |
--------------------------------------------------------------------------------
/model/src/machine/rails.rs:
--------------------------------------------------------------------------------
1 | use crate::physics::Length;
2 |
3 | /// Let's just assume MGN15 for now, unless it turns out not to be sufficient.
4 | pub fn mgn15_height_total() -> Length {
5 | // See HIWIN catalogue table 3.79 on page 97/
6 | Length::from_value_mm(16.)
7 | }
8 |
--------------------------------------------------------------------------------
/model/src/machine/spindle.rs:
--------------------------------------------------------------------------------
1 | use crate::physics::{Power, RotationalSpeed, Torque};
2 |
3 | pub struct Spindle {
4 | power: Power,
5 | }
6 |
7 | impl Spindle {
8 | const MIN: RotationalSpeed = RotationalSpeed::from_value_rpm(5000.);
9 | const MAX: RotationalSpeed = RotationalSpeed::from_value_rpm(24000.);
10 |
11 | pub fn new(power: Power) -> Self {
12 | Self { power }
13 | }
14 |
15 | /// Calculate spindle torque in Nm at a given speed in rpm
16 | pub fn torque(&self, rotational_speed: RotationalSpeed) -> Torque {
17 | let rotational_speed = rotational_speed.clamp(Self::MIN, Self::MAX);
18 | self.power.to_torque(rotational_speed)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/model/src/physics.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | f64::consts::{PI, TAU},
3 | fmt,
4 | ops::{Add, Div},
5 | };
6 |
7 | /// A diameter
8 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
9 | pub struct Diameter(Length);
10 |
11 | impl Diameter {
12 | /// Create an instance of `Diameter` from a `Length`
13 | pub const fn from_length(length: Length) -> Self {
14 | Self(length)
15 | }
16 |
17 | /// Convert this diameter into a `Length`
18 | pub fn to_length(&self) -> Length {
19 | self.0
20 | }
21 |
22 | /// Convert this diameter into a `Radius`
23 | pub fn to_radius(&self) -> Radius {
24 | Radius::from_length(self.to_length() / 2.)
25 | }
26 | }
27 |
28 | /// A force
29 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
30 | pub struct Force(f64);
31 |
32 | impl Force {
33 | /// Create an instance of `Force` from a value in Newton
34 | pub const fn from_value_n(force_n: f64) -> Self {
35 | Self(force_n)
36 | }
37 |
38 | /// Return the value in Newton
39 | pub fn value_n(&self) -> f64 {
40 | self.0
41 | }
42 |
43 | /// Compute the torque resulting from this force at the given radius
44 | pub fn to_torque(&self, radius: impl Into) -> Torque {
45 | let torque_nm = self.value_n() * radius.into().to_length().value_m();
46 | Torque::from_value_nm(torque_nm)
47 | }
48 | }
49 |
50 | impl fmt::Display for Force {
51 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
52 | write!(f, "{:.2} N", self.value_n())
53 | }
54 | }
55 |
56 | /// A length
57 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
58 | pub struct Length(f64);
59 |
60 | impl Length {
61 | /// Create an instance of `Length` from a value in meter
62 | pub const fn from_value_m(length_m: f64) -> Self {
63 | Self(length_m)
64 | }
65 |
66 | /// Create an instance of `Length` from a value in millimeter
67 | pub fn from_value_mm(length_mm: f64) -> Self {
68 | Self::from_value_m(length_mm / 1000.)
69 | }
70 |
71 | /// Return the value in meter
72 | pub fn value_m(&self) -> f64 {
73 | self.0
74 | }
75 |
76 | /// Return the value in millimeter
77 | pub fn value_mm(&self) -> f64 {
78 | self.0 * 1000.
79 | }
80 | }
81 |
82 | impl Add for Length {
83 | type Output = Self;
84 |
85 | fn add(self, rhs: Self) -> Self::Output {
86 | Self(self.0 + rhs.0)
87 | }
88 | }
89 |
90 | impl Div for Length {
91 | type Output = Self;
92 |
93 | fn div(self, rhs: f64) -> Self::Output {
94 | Self(self.0 / rhs)
95 | }
96 | }
97 |
98 | /// A power value
99 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
100 | pub struct Power(f64);
101 |
102 | impl Power {
103 | pub const fn from_value_w(power_w: f64) -> Self {
104 | Self(power_w)
105 | }
106 |
107 | pub fn from_value_kw(power_kw: f64) -> Self {
108 | Self::from_value_w(power_kw * 1000.)
109 | }
110 |
111 | pub fn value_w(&self) -> f64 {
112 | self.0
113 | }
114 |
115 | pub fn to_torque(&self, rotational_speed: RotationalSpeed) -> Torque {
116 | Torque::from_value_nm(
117 | self.value_w() / rotational_speed.value_rad_per_s(),
118 | )
119 | }
120 | }
121 |
122 | /// A radius
123 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
124 | pub struct Radius(Length);
125 |
126 | impl Radius {
127 | /// Create an instance of `Radius` from a `Length`
128 | pub const fn from_length(length: Length) -> Self {
129 | Self(length)
130 | }
131 |
132 | /// Convert this radius into a `Length`
133 | pub fn to_length(&self) -> Length {
134 | self.0
135 | }
136 | }
137 |
138 | impl From for Radius {
139 | fn from(diameter: Diameter) -> Self {
140 | diameter.to_radius()
141 | }
142 | }
143 |
144 | impl Add for Radius {
145 | type Output = Self;
146 |
147 | fn add(self, rhs: Self) -> Self::Output {
148 | Self(self.0 + rhs.0)
149 | }
150 | }
151 |
152 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
153 | pub struct RotationalSpeed(f64);
154 |
155 | impl RotationalSpeed {
156 | pub const fn from_value_rpm(rotational_speed_rpm: f64) -> Self {
157 | Self(rotational_speed_rpm)
158 | }
159 |
160 | pub fn value_rpm(&self) -> f64 {
161 | self.0
162 | }
163 |
164 | pub fn value_rad_per_s(&self) -> f64 {
165 | self.value_rpm() / 60. * 2. * PI
166 | }
167 |
168 | pub fn clamp(&self, min: RotationalSpeed, max: RotationalSpeed) -> Self {
169 | Self(self.0.clamp(min.0, max.0))
170 | }
171 | }
172 |
173 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
174 | pub struct Speed(f64);
175 |
176 | impl Speed {
177 | pub const fn from_value_m_per_s(speed_m_per_s: f64) -> Self {
178 | Self(speed_m_per_s)
179 | }
180 |
181 | pub fn from_value_m_per_min(speed_m_per_min: f64) -> Self {
182 | Self::from_value_m_per_s(speed_m_per_min / 60.)
183 | }
184 |
185 | pub fn value_m_per_min(&self) -> f64 {
186 | self.0 * 60.
187 | }
188 |
189 | pub fn to_rotational_speed(
190 | &self,
191 | radius: impl Into,
192 | ) -> RotationalSpeed {
193 | RotationalSpeed::from_value_rpm(
194 | self.value_m_per_min() / radius.into().to_length().value_m() / TAU,
195 | )
196 | }
197 | }
198 |
199 | /// A torque
200 | #[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
201 | pub struct Torque(f64);
202 |
203 | impl Torque {
204 | /// Create an instance of `Torque` from a value in Nm
205 | pub const fn from_value_nm(torque_nm: f64) -> Self {
206 | Self(torque_nm)
207 | }
208 |
209 | /// Return the value in Nm
210 | pub fn value_nm(&self) -> f64 {
211 | self.0
212 | }
213 |
214 | /// Compute the force resulting from this torque at the given radius
215 | pub fn to_force(&self, radius: impl Into) -> Force {
216 | let force_n = self.value_nm() / radius.into().to_length().value_m();
217 | Force::from_value_n(force_n)
218 | }
219 | }
220 |
221 | impl fmt::Display for Torque {
222 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
223 | write!(f, "{:.2} Nm", self.value_nm())
224 | }
225 | }
226 |
--------------------------------------------------------------------------------
/model/src/tools.rs:
--------------------------------------------------------------------------------
1 | use std::collections::BTreeMap;
2 |
3 | use crate::physics::{Diameter, Force, Length, RotationalSpeed, Speed, Torque};
4 |
5 | #[derive(Debug)]
6 | pub struct Tool {
7 | pub diameter: Diameter,
8 | pub length_cutting_edge: Length,
9 | pub length_total: Length,
10 | pub num_flutes: f64,
11 | }
12 |
13 | impl Tool {
14 | pub fn tools() -> Vec {
15 | // This should be a representative selection of tools. I've been trying
16 | // to find combinations of the smallest diameter and longest length. See
17 | // research notes.
18 |
19 | macro_rules! tools {
20 | ($(
21 | Self {
22 | diameter: $diameter:expr,
23 | length_cutting_edge: $length_cutting_edge:expr,
24 | length_total: $length_total:expr,
25 | num_flutes: $num_flutes:expr,
26 | },
27 | )*) => {
28 | vec![
29 | $(
30 | {
31 | let diameter = Diameter::from_length(
32 | Length::from_value_mm($diameter),
33 | );
34 | let length_cutting_edge =
35 | Length::from_value_mm($length_cutting_edge);
36 | let length_total =
37 | Length::from_value_mm($length_total);
38 |
39 | Self {
40 | diameter,
41 | length_cutting_edge,
42 | length_total,
43 | num_flutes: $num_flutes,
44 | }
45 | },
46 | )*
47 | ]
48 | };
49 | }
50 |
51 | tools![
52 | // https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-8-werkzeuge/3-175----1-8---Fraeser/2-Schneider-ALU/
53 | Self {
54 | diameter: 0.4,
55 | length_cutting_edge: 2.0,
56 | length_total: 38.0,
57 | num_flutes: 2.,
58 | },
59 | Self {
60 | diameter: 0.5,
61 | length_cutting_edge: 2.5,
62 | length_total: 38.0,
63 | num_flutes: 2.,
64 | },
65 | Self {
66 | diameter: 0.6,
67 | length_cutting_edge: 3.0,
68 | length_total: 38.0,
69 | num_flutes: 2.,
70 | },
71 | Self {
72 | diameter: 1.5,
73 | length_cutting_edge: 12.0,
74 | length_total: 38.0,
75 | num_flutes: 2.,
76 | },
77 | Self {
78 | diameter: 1.6,
79 | length_cutting_edge: 5.0,
80 | length_total: 38.0,
81 | num_flutes: 2.,
82 | },
83 | Self {
84 | diameter: 1.8,
85 | length_cutting_edge: 6.0,
86 | length_total: 38.0,
87 | num_flutes: 2.,
88 | },
89 | Self {
90 | diameter: 2.0,
91 | length_cutting_edge: 12.0,
92 | length_total: 38.0,
93 | num_flutes: 2.,
94 | },
95 | Self {
96 | diameter: 2.4,
97 | length_cutting_edge: 7.0,
98 | length_total: 38.0,
99 | num_flutes: 2.,
100 | },
101 | Self {
102 | diameter: 2.5,
103 | length_cutting_edge: 15.0,
104 | length_total: 38.0,
105 | num_flutes: 2.,
106 | },
107 | Self {
108 | diameter: 3.175,
109 | length_cutting_edge: 5.0,
110 | length_total: 8.0,
111 | num_flutes: 2.,
112 | },
113 | // https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-schneider/Schaftfraeser-ALU-412/
114 | Self {
115 | diameter: 3.0,
116 | length_cutting_edge: 22.0,
117 | length_total: 50.0,
118 | num_flutes: 1.,
119 | },
120 | Self {
121 | diameter: 6.0,
122 | length_cutting_edge: 26.0,
123 | length_total: 68.0,
124 | num_flutes: 1.,
125 | },
126 | Self {
127 | diameter: 6.0,
128 | length_cutting_edge: 21.0,
129 | length_total: 80.0,
130 | num_flutes: 1.,
131 | },
132 | Self {
133 | diameter: 10.0,
134 | length_cutting_edge: 26.0,
135 | length_total: 110.0,
136 | num_flutes: 1.,
137 | },
138 | // https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-schneider/1-Schneider-Sorotec-PROALU/
139 | Self {
140 | diameter: 2.0,
141 | length_cutting_edge: 8.0,
142 | length_total: 50.0,
143 | num_flutes: 1.,
144 | },
145 | // https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/1-schneider/einschneider-sorotec-alu-beschichtet/
146 | Self {
147 | diameter: 1.0,
148 | length_cutting_edge: 5.0,
149 | length_total: 40.0,
150 | num_flutes: 1.,
151 | },
152 | Self {
153 | diameter: 2.0,
154 | length_cutting_edge: 5.0,
155 | length_total: 40.0,
156 | num_flutes: 1.,
157 | },
158 | // https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/2-schneider/Schaftfraeser-ALU/
159 | Self {
160 | diameter: 4.0,
161 | length_cutting_edge: 21.0,
162 | length_total: 70.0,
163 | num_flutes: 2.,
164 | },
165 | Self {
166 | diameter: 5.0,
167 | length_cutting_edge: 30.0,
168 | length_total: 75.0,
169 | num_flutes: 2.,
170 | },
171 | Self {
172 | diameter: 6.0,
173 | length_cutting_edge: 30.0,
174 | length_total: 75.0,
175 | num_flutes: 2.,
176 | },
177 | Self {
178 | diameter: 8.0,
179 | length_cutting_edge: 40.0,
180 | length_total: 100.0,
181 | num_flutes: 2.,
182 | },
183 | Self {
184 | diameter: 10.0,
185 | length_cutting_edge: 40.0,
186 | length_total: 100.0,
187 | num_flutes: 2.,
188 | },
189 | Self {
190 | diameter: 12.0,
191 | length_cutting_edge: 32.0,
192 | length_total: 74.0,
193 | num_flutes: 2.,
194 | },
195 | // https://www.sorotec.de/shop/Zerspanungswerkzeuge/sorotec-werkzeuge/RADIENFRAeSER/1-Schneider-PRO/
196 | Self {
197 | diameter: 2.0,
198 | length_cutting_edge: 6.0,
199 | length_total: 39.0,
200 | num_flutes: 1.,
201 | },
202 | ]
203 | }
204 |
205 | pub fn desired_rpm(&self) -> RotationalSpeed {
206 | // Cutting speed for aluminium. See this document:
207 | // https://www.sorotec.de/webshop/Datenblaetter/fraeser/schnittwerte.pdf
208 | let cutting_speed = Speed::from_value_m_per_min(500.);
209 |
210 | cutting_speed.to_rotational_speed(self.diameter)
211 | }
212 |
213 | pub fn feed_per_tooth(&self) -> Length {
214 | // Based on the table on page 2 of this document:
215 | // https://www.sorotec.de/webshop/Datenblaetter/fraeser/schnittwerte.pdf
216 | //
217 | // There are two rows for aluminium. We're choosing the higher values
218 | // here, since those represent the worst case for our calculation.
219 |
220 | macro_rules! table {
221 | (
222 | $(
223 | $diameter:expr, $feed_per_tooth_mm:expr;
224 | )*
225 | ) => {
226 | {
227 | let mut feed_per_tooth = BTreeMap::new();
228 |
229 | $(
230 | feed_per_tooth.insert($diameter, $feed_per_tooth_mm);
231 | )*
232 |
233 | feed_per_tooth
234 | }
235 | };
236 | }
237 |
238 | let feed_per_tooth = table!(
239 | 1, 0.010;
240 | 2, 0.020;
241 | 3, 0.025;
242 | 4, 0.050;
243 | 5, 0.050;
244 | 6, 0.050;
245 | 8, 0.064;
246 | 10, 0.080;
247 | 12, 0.100;
248 | );
249 |
250 | let length_mm = *feed_per_tooth
251 | .get(&(self.diameter.to_length().value_mm().ceil() as u8))
252 | .unwrap();
253 |
254 | Length::from_value_mm(length_mm)
255 | }
256 |
257 | pub fn tangential_cutting_force(&self) -> (Force, Torque) {
258 | // This article talks about tangential cutting force:
259 | // https://www.ctemag.com/news/articles/understanding-tangential-cutting-force-when-milling
260 | //
261 | // It gives the following formula; (2) in the article:
262 | // Ft = sigma * A * Zc * Ef * Tf
263 | //
264 | // Ft: tangential cutting force
265 | // sigma: ultimate tensile strength (σ)
266 | // A: cross-sectional area of the uncut chip
267 | // Zc: number of teeth engaged in workpiece
268 | // Ef: engagement factor of workpiece material
269 | // Tf: cutting tool wear factor
270 | //
271 | // Wikipedia has an article on ultimate tensile strength:
272 | // https://en.wikipedia.org/wiki/Ultimate_tensile_strength
273 | //
274 | // According to the table in there, this is the value for aluminium:
275 | let sigma = 483_000_000.; // Pascal
276 |
277 | // The cross-sectional area of the uncut chip depends on axial depth
278 | // of cut. There's information about that in this document:
279 | // https://www.sorotec.de/webshop/Datenblaetter/fraeser/schnittwerte.pdf
280 | //
281 | // For our calculation, the side milling case is the worst case, due
282 | // to the higher axial depth of cut.
283 | let axial_depth_of_cut = self.length_cutting_edge.value_m();
284 | let a = axial_depth_of_cut * self.feed_per_tooth().value_m();
285 |
286 | // For the number of engaged teeth, let's just go with the worst
287 | // case: At most, the engagement angle is 180°, and the number of
288 | // engaged teeth is half the total number of teeth.
289 | let z_c = (self.num_flutes / 2.).ceil();
290 |
291 | // I don't quite understand what the engagement factor is, but if
292 | // I'm reading the article right, it's just the radial depth of cut
293 | // divided by cutting diameter.
294 | //
295 | // Radial depth of cut is supposed to be 25% of the cutter diameter
296 | // for the side milling case we're looking at, according to the
297 | // Sorotec document linked above.
298 | let e_f = 0.25;
299 |
300 | // As for cutting tool wear factor, I might be misunderstanding the
301 | // article, but I think the following should be a good worst case.
302 | let t_f = 1.6;
303 |
304 | // Now put it all together to calculate the tangential cutting
305 | // force.
306 | let tangential_cutting_force =
307 | Force::from_value_n(sigma * a * z_c * e_f * t_f);
308 |
309 | let torque = tangential_cutting_force.to_torque(self.diameter);
310 |
311 | (tangential_cutting_force, torque)
312 | }
313 | }
314 |
--------------------------------------------------------------------------------
/rustfmt.toml:
--------------------------------------------------------------------------------
1 | max_width = 80
2 |
--------------------------------------------------------------------------------