openscad.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. # Source code for the book "Python for 3D Printing", by John Clark Craig
  2. ####################################################################################################
  3. #
  4. # openscad.py
  5. #
  6. ####################################################################################################
  7. import os
  8. import math
  9. import inspect
  10. # Global commands accumulator
  11. _cmds = ""
  12. _use = ""
  13. # Function modifying parameters
  14. fragments = 31
  15. # Constants
  16. tiny = 1e-99
  17. # Functions for driving Openscad
  18. def startup(s):
  19. global _use
  20. _use += f"""{s}\n"""
  21. def literally(s):
  22. global _cmds
  23. _cmds += f"""{s}\n"""
  24. def cyl(diameter, height):
  25. global _cmds, fragments
  26. radius = diameter / 2
  27. _cmds = (
  28. f"cylinder(h={height},"
  29. f"r1={radius},r2={radius},"
  30. f"center=false,$fn={fragments});\n\n"
  31. ) + _cmds
  32. def cylinder(diameter, height):
  33. global _cmds, fragments
  34. radius = diameter / 2
  35. _cmds = (
  36. f"cylinder(h={height},"
  37. f"r1={radius},r2={radius},"
  38. f"center=false,$fn={fragments});\n\n"
  39. ) + _cmds
  40. def cone(diameter, height):
  41. global _cmds, fragments
  42. radius = diameter / 2
  43. _cmds = (
  44. f"cylinder(h={height},"
  45. f"r1={radius},r2={0},"
  46. f"center=false,"
  47. f"$fn={fragments});\n\n"
  48. ) + _cmds
  49. def cone_truncated(diameter1, diameter2, height):
  50. global _cmds, fragments
  51. radius = diameter1 / 2
  52. radius2 = diameter2 / 2
  53. _cmds = (
  54. f"cylinder(h={height},r1={radius},"
  55. f"r2={radius2},center=false,"
  56. f"$fn={fragments});\n\n"
  57. ) + _cmds
  58. def sphere(diameter):
  59. global _cmds, fragments
  60. radius = diameter / 2
  61. _cmds = (f"sphere({radius},"
  62. f"$fn={fragments});\n\n" ) + _cmds
  63. def box(x, y, z):
  64. global _cmds
  65. _cmds = (f"cube({[x,y,z]},"
  66. f"center=false);\n\n") + _cmds
  67. def polygon(points_list, height):
  68. global _cmds, fragments
  69. _cmds = (
  70. f"linear_extrude({height})\n"
  71. f"polygon({points_list},"
  72. f"convexity=20,$fn={fragments});\n\n"
  73. ) + _cmds
  74. def triangle(point1, point2, point3, height):
  75. polygon([point1, point2, point3], height)
  76. def regular_polygon(sides, radius, height):
  77. global _cmds
  78. _cmds = "}\n\n" + _cmds
  79. for wedge in range(sides):
  80. p1 = _cart(radius,wedge*360/sides)
  81. p2 = _cart(radius,(wedge+1)*360/sides)
  82. triangle([0, 0], p1, p2, height)
  83. _cmds = "union(){\n" + _cmds
  84. def tube(outside_diam, inside_diam, height):
  85. global _cmds, fragments
  86. r1 = outside_diam / 2
  87. r2 = inside_diam / 2
  88. _cmds = (
  89. "difference(){\n"
  90. f"cylinder(h={height},r1={r1},r2={r1},"
  91. f"center=false,$fn={fragments});\n"
  92. f"cylinder(h={height*3},r1={r2},r2={r2},"
  93. f"center=true,$fn={fragments});\n"
  94. "}\n" ) + _cmds
  95. def polyhedron(points_list, faces_list):
  96. global _cmds
  97. _cmds = (f"polyhedron({points_list},"
  98. f"{faces_list},convexity=20);\n\n"
  99. ) + _cmds
  100. def text(
  101. text="",
  102. size=10,
  103. font="Liberation Sans",
  104. halign="left",
  105. valign="baseline",
  106. spacing=1,
  107. direction="ltr",
  108. language="en",
  109. script="latin",
  110. height=1,
  111. ):
  112. global _cmds, fragments
  113. _cmds = (
  114. f'linear_extrude({height})\n'
  115. f'text(text="{text}",size={size},'
  116. f'font="{font}",halign="{halign}",'
  117. f'valign="{valign}",spacing={spacing},'
  118. f'direction="{direction}",'
  119. f'language="{language}",'
  120. f'script="{script}",'
  121. f'$fn={fragments});\n\n'
  122. ) + _cmds
  123. def translate(x, y, z):
  124. global _cmds
  125. _cmds = f"translate([{x},{y},{z}])\n" + _cmds
  126. def rotate(x, y, z):
  127. global _cmds
  128. _cmds = f"rotate([{x},{y},{z}])\n" + _cmds
  129. def scale(x, y, z):
  130. global _cmds
  131. _cmds = f"scale([{x},{y},{z}])\n" + _cmds
  132. def resize(x, y, z):
  133. global _cmds
  134. _cmds = f"resize([{x},{y},{z}])\n" + _cmds
  135. def mirror(x, y, z):
  136. global _cmds
  137. _cmds = f"mirror([{x},{y},{z}])\n" + _cmds
  138. def color(color_name, alpha=1.0):
  139. global _cmds
  140. _cmds = (f'color("{color_name}",'
  141. f'{alpha})\n') + _cmds
  142. def rgb(r, g, b, alpha=1.0):
  143. global _cmds
  144. _cmds = (f"color([{r/255},{g/255},"
  145. f"{b/255},{alpha}])\n") + _cmds
  146. def offset_round(distance, height):
  147. global _cmds, fragments
  148. _cmds = (
  149. f"linear_extrude({height})\n"
  150. f"offset(r={distance},chamfer=false,"
  151. f"$fn={fragments})\n"
  152. f"projection()\n" ) + _cmds
  153. def offset_straight(distance, height):
  154. global _cmds
  155. _cmds = (
  156. f"linear_extrude({height})\n"
  157. f"offset(delta={distance},"
  158. f"chamfer=false)\n"
  159. f"projection()\n"
  160. ) + _cmds
  161. def offset_chamfer(distance, height):
  162. global _cmds
  163. _cmds = (
  164. f"linear_extrude({height})\n"
  165. f"offset(delta={distance},"
  166. f"chamfer=true)\n"
  167. f"projection()\n"
  168. ) + _cmds
  169. def projection(height):
  170. global _cmds
  171. _cmds = (f"linear_extrude({height})"
  172. f"\nprojection()\n") + _cmds
  173. def slice(height):
  174. global _cmds
  175. _cmds = (f"linear_extrude({height})\n"
  176. f"projection(cut=true)\n") + _cmds
  177. def spiral(turns, height, scale=1):
  178. global _cmds, fragments
  179. _cmds = (
  180. f"linear_extrude(height={height},"
  181. f"twist=-{360*turns},"
  182. f"scale={scale},"
  183. f"slices={fragments},"
  184. f"center=false,"
  185. f"convexity=20,"
  186. f"$fn={fragments})\n"
  187. f"projection()\n"
  188. ) + _cmds
  189. def rotate_extrude(angle=360):
  190. global _cmds, fragments
  191. _cmds = (
  192. f"rotate_extrude(angle={angle},"
  193. f"convexity=20,$fn={fragments})\n"
  194. f"projection()\n"
  195. ) + _cmds
  196. def union(obj_list):
  197. global _cmds
  198. cmd = "union(){\n"
  199. for obj in obj_list:
  200. cmd += obj
  201. cmd += "}\n"
  202. _cmds = cmd + _cmds
  203. def difference(obj_list):
  204. global _cmds
  205. cmd = "difference(){\n"
  206. for obj in obj_list:
  207. cmd += obj
  208. cmd += "}\n"
  209. _cmds = cmd + _cmds
  210. def intersection(obj_list):
  211. global _cmds
  212. cmd = "intersection(){\n"
  213. for obj in obj_list:
  214. cmd += obj
  215. cmd += "}\n"
  216. _cmds = cmd + _cmds
  217. def hull(obj_list):
  218. global _cmds
  219. cmd = "hull(){\n"
  220. for obj in obj_list:
  221. cmd += obj
  222. cmd += "}\n"
  223. _cmds = cmd + _cmds
  224. def minkowski(obj_list):
  225. global _cmds
  226. cmd = "minkowski(){\n"
  227. for obj in obj_list:
  228. cmd += obj
  229. cmd += "}\n"
  230. _cmds = cmd + _cmds
  231. def surface(filename,invert="false"):
  232. global _cmds
  233. _cmds = (f'surface(file=\"{filename}\",'
  234. f'convexity=5,'
  235. f'invert={invert});\n') + _cmds
  236. def result():
  237. global _cmds
  238. res = _cmds
  239. _cmds = ""
  240. return res
  241. def output(cmds_list=""):
  242. global _use
  243. calling_file = inspect.stack()[1].filename
  244. srcfile = calling_file.split("\\")[-1]
  245. dstfile = srcfile.replace(".py", ".scad")
  246. f = open(dstfile, "w")
  247. if _use:
  248. f.write(_use)
  249. _use = ""
  250. for cmd in cmds_list:
  251. f.write(cmd)
  252. f.close()
  253. def platonic_cube(n):
  254. box(n, n, n)
  255. translate(-n / 2, -n / 2, -n / 2)
  256. def platonic_tetrahedron(n):
  257. n0 = n * (8 / 3) ** -0.5
  258. n1 = ((8 / 9) ** 0.5) * n0
  259. n2 = ((2 / 9) ** 0.5) * n0
  260. n3 = ((2 / 3) ** 0.5) * n0
  261. n4 = -n0 / 3
  262. v0 = [n1, 0, n4]
  263. v1 = [-n2, n3, n4]
  264. v2 = [-n2, -n3, n4]
  265. v3 = [0, 0, n0]
  266. points = [v0, v1, v2, v3]
  267. faces = [[0, 1, 2], [0, 3, 1],
  268. [1, 3, 2], [2, 3, 0]]
  269. polyhedron(points, faces)
  270. def platonic_octahedron(n):
  271. n0 = n * 2 ** -0.5
  272. v0 = [n0, 0, 0]
  273. v1 = [-n0, 0, 0]
  274. v2 = [0, n0, 0]
  275. v3 = [0, -n0, 0]
  276. v4 = [0, 0, n0]
  277. v5 = [0, 0, -n0]
  278. points = [v0, v1, v2, v3, v4, v5]
  279. faces = [
  280. [0, 4, 2],
  281. [2, 4, 1],
  282. [1, 4, 3],
  283. [3, 4, 0],
  284. [2, 5, 0],
  285. [1, 5, 2],
  286. [3, 5, 1],
  287. [0, 5, 3],
  288. ]
  289. polyhedron(points, faces)
  290. def platonic_dodecahedron(n):
  291. phi = (1 + 5 ** 0.5) / 2
  292. fac = n * phi / 2
  293. n0 = phi * fac
  294. n1 = fac / phi
  295. v0 = [fac, fac, fac]
  296. v1 = [-fac, fac, fac]
  297. v2 = [fac, -fac, fac]
  298. v3 = [-fac, -fac, fac]
  299. v4 = [fac, fac, -fac]
  300. v5 = [-fac, fac, -fac]
  301. v6 = [fac, -fac, -fac]
  302. v7 = [-fac, -fac, -fac]
  303. v8 = [0, n0, n1]
  304. v9 = [0, -n0, n1]
  305. v10 = [0, n0, -n1]
  306. v11 = [0, -n0, -n1]
  307. v12 = [n1, 0, n0]
  308. v13 = [n1, 0, -n0]
  309. v14 = [-n1, 0, n0]
  310. v15 = [-n1, 0, -n0]
  311. v16 = [n0, n1, 0]
  312. v17 = [-n0, n1, 0]
  313. v18 = [n0, -n1, 0]
  314. v19 = [-n0, -n1, 0]
  315. points = [
  316. v0,
  317. v1,
  318. v2,
  319. v3,
  320. v4,
  321. v5,
  322. v6,
  323. v7,
  324. v8,
  325. v9,
  326. v10,
  327. v11,
  328. v12,
  329. v13,
  330. v14,
  331. v15,
  332. v16,
  333. v17,
  334. v18,
  335. v19,
  336. ]
  337. f1 = [0, 16, 18, 2, 12]
  338. f2 = [14, 12, 2, 9, 3]
  339. f3 = [19, 3, 9, 11, 7]
  340. f4 = [15, 7, 11, 6, 13]
  341. f5 = [4, 13, 6, 18, 16]
  342. f6 = [2, 18, 6, 11, 9]
  343. f7 = [12, 14, 1, 8, 0]
  344. f8 = [3, 19, 17, 1, 14]
  345. f9 = [7, 15, 5, 17, 19]
  346. f10 = [13, 4, 10, 5, 15]
  347. f11 = [16, 0, 8, 10, 4]
  348. f12 = [1, 17, 5, 10, 8]
  349. faces = [f1, f2, f3, f4, f5, f6,
  350. f7, f8, f9, f10, f11, f12]
  351. polyhedron(points, faces)
  352. def platonic_icosahedron(n):
  353. phi = (1 + 5 ** 0.5) / 2
  354. fac = n / 2
  355. n0 = phi * fac
  356. v0 = [0, fac, n0]
  357. v1 = [0, fac, -n0]
  358. v2 = [0, -fac, n0]
  359. v3 = [0, -fac, -n0]
  360. v4 = [fac, n0, 0]
  361. v5 = [fac, -n0, 0]
  362. v6 = [-fac, n0, 0]
  363. v7 = [-fac, -n0, 0]
  364. v8 = [n0, 0, fac]
  365. v9 = [-n0, 0, fac]
  366. v10 = [n0, 0, -fac]
  367. v11 = [-n0, 0, -fac]
  368. points = [v0, v1, v2, v3, v4, v5, v6,
  369. v7, v8, v9, v10, v11]
  370. f1 = [2, 9, 0]
  371. f2 = [2, 7, 9]
  372. f3 = [2, 5, 7]
  373. f4 = [2, 8, 5]
  374. f5 = [2, 0, 8]
  375. f6 = [1, 4, 6]
  376. f7 = [1, 6, 11]
  377. f8 = [1, 11, 3]
  378. f9 = [1, 3, 10]
  379. f10 = [1, 10, 4]
  380. f11 = [0, 6, 4]
  381. f12 = [6, 0, 9]
  382. f13 = [9, 11, 6]
  383. f14 = [11, 9, 7]
  384. f15 = [7, 3, 11]
  385. f16 = [3, 7, 5]
  386. f17 = [5, 10, 3]
  387. f18 = [10, 8, 5]
  388. f19 = [8, 4, 10]
  389. f20 = [4, 8, 0]
  390. faces = [
  391. f1,
  392. f2,
  393. f3,
  394. f4,
  395. f5,
  396. f6,
  397. f7,
  398. f8,
  399. f9,
  400. f10,
  401. f11,
  402. f12,
  403. f13,
  404. f14,
  405. f15,
  406. f16,
  407. f17,
  408. f18,
  409. f19,
  410. f20,
  411. ]
  412. polyhedron(points, faces)
  413. # Local utility functions
  414. def _cart(radius, angle):
  415. rad = math.radians(angle)
  416. x = radius * math.cos(rad)
  417. y = radius * math.sin(rad)
  418. return [x, y]