├── .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 | --------------------------------------------------------------------------------