piecad_viewer 1.0.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ dist
2
+ .vscode
3
+ lib
4
+ bin
5
+ share
6
+ lib64
7
+ .benchmarks
8
+ include
9
+ __pycache__
10
+ test.py
11
+ pyvenv.cfg
12
+ *.obj
@@ -0,0 +1,30 @@
1
+ The bulk of the code is covered by this license:
2
+
3
+ The MIT License (MIT)
4
+
5
+ Copyright (c) 2025 Brian Sturgill
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in
15
+ all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
+ THE SOFTWARE.
24
+ ----
25
+
26
+ The file `piecad/_poly_point_isect.py` has it's own license (located at the top of the file).
27
+
28
+ ----
29
+
30
+ Fonts are covered by a separate license, see the License files in piecad/fonts for details.
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.1
2
+ Name: piecad_viewer
3
+ Version: 1.0.0
4
+ Summary: "Viewer for Easy as Pie" CAD (Piecad)
5
+ Author-email: Brian Sturgill <codersnext@gmail.com>
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Requires-Dist: pyglet == 1.5.31
8
+ Requires-Dist: trimesh >= 1.26.4
9
+ Requires-Dist: glooey >= 0.3.6
10
+ Requires-Dist: scipy >= 1.15.2
11
+ Project-URL: Home, https://github.com/briansturgill/Piecad-Viewer
@@ -0,0 +1,24 @@
1
+ # Viewer for "Easy as Pie" CAD (Piecad)
2
+
3
+ [Piecad](https://github.com/briansturgill/Piecad) is a CAD API used to construct 3D models in [Python](https://www.python.org).
4
+ Its primary focus is the creation of models for 3D printing.
5
+
6
+ To use Piecad-Viewer, run `piecad-viewer` and then use the `view` function in Piecad, which works like a 3d `print` (also does 2D).
7
+ Piecad-Viewer allows you to see the output from multiple `view` calls.
8
+
9
+ Type `h` in the Piecad-Viewer window for a list of commands.
10
+
11
+ To install:
12
+
13
+ ```sh
14
+ pip install piecad_viewer
15
+
16
+ # This will install the script:
17
+ piecad_viewer
18
+
19
+ ```
20
+
21
+ ## CREDITS
22
+
23
+ Piecad viewer is based on [trimesh](https://github.com/mikedh/trimesh)'s viewer, which in
24
+ turn is based on version 1 of [pyglet](https://github.com/pyglet/pyglet).
@@ -0,0 +1,215 @@
1
+ import pyglet
2
+ from . import server
3
+ from threading import Thread, Lock
4
+ import json
5
+ import numpy as np
6
+ import trimesh
7
+ from trimesh.viewer import windowed
8
+ import os
9
+
10
+ view_lock = Lock()
11
+
12
+
13
+ def display_help():
14
+ from tkinter import Tk, Label
15
+
16
+ text_l = [
17
+ "",
18
+ "Key Action",
19
+ "---------------------------------------------",
20
+ "a Toggle axis.",
21
+ "c Toggle culling.",
22
+ "f Toggle fullscreen.",
23
+ "g Toggle grid.",
24
+ "h, ? or ESC View/dismiss this help.",
25
+ "q Quit CAD Viewer.",
26
+ "w Toggle wireframe.",
27
+ "z Reset view.",
28
+ "LEFT Move to the previous object.",
29
+ "RIGHT Move to the next object.",
30
+ "SHIFT-m Launch meshlab on current object.",
31
+ "SHIFT-LEFT Rotate image left.",
32
+ "SHIFT-RIGHT Rotate image right.",
33
+ "SHIFT-UP Rotate image up.",
34
+ "SHIFT-DOWN Rotate image down.",
35
+ "",
36
+ ]
37
+ longest = 0
38
+ for t in text_l:
39
+ if longest < len(t):
40
+ longest = len(t)
41
+ root = Tk()
42
+
43
+ def keydown(e):
44
+ ch = e.char
45
+ if ch == "h" or ch == "?" or ord(ch) == 27 or ord(ch) == 13:
46
+ root.destroy()
47
+
48
+ root.bind("<KeyPress>", keydown)
49
+
50
+ x = (longest) * 14
51
+ y = (len(text_l) + 10) * 14
52
+ root.geometry(f"{x}x{y}")
53
+ root.title("CAD Viewer Help")
54
+ text = Label(
55
+ root,
56
+ height=len(text_l),
57
+ justify="left",
58
+ text="\n".join(text_l),
59
+ font=("Courier", 14, "bold"),
60
+ )
61
+ text.pack(padx=10, pady=10)
62
+ root.mainloop()
63
+
64
+
65
+ display_needed = False
66
+ cur_idx = 0
67
+ meshes = []
68
+ titles = []
69
+
70
+
71
+ class MySceneViewer(trimesh.viewer.windowed.SceneViewer):
72
+ def __init__(self, scene, **kwargs):
73
+ super().__init__(scene, **kwargs)
74
+
75
+ def on_key_press(self, symbol, modifiers):
76
+ global cur_idx, meshes, display_needed
77
+ ctrl = modifiers & pyglet.window.key.MOD_CTRL
78
+ shift = modifiers & pyglet.window.key.MOD_SHIFT
79
+ if symbol == pyglet.window.key.W:
80
+ self.toggle_wireframe()
81
+ elif symbol == pyglet.window.key.Z:
82
+ self.reset_view()
83
+ elif symbol == pyglet.window.key.C:
84
+ self.toggle_culling()
85
+ elif symbol == pyglet.window.key.A:
86
+ self.toggle_axis()
87
+ elif symbol == pyglet.window.key.G:
88
+ self.toggle_grid()
89
+ elif symbol == pyglet.window.key.Q:
90
+ self.on_close()
91
+ elif symbol == pyglet.window.key.M:
92
+ if shift:
93
+ tmpfile = (
94
+ "/tmp/cadviewer_tmp.obj" # LATER surely I can do better for temp?
95
+ )
96
+ saved = False
97
+ view_lock.acquire()
98
+ if len(meshes) > 0:
99
+ trimesh.exchange.export.export_mesh(meshes[cur_idx], tmpfile, "obj")
100
+ saved = True
101
+ view_lock.release()
102
+ if saved:
103
+ os.system("meshlab " + tmpfile)
104
+ return # Doesn't really work right, supress.
105
+ elif symbol == pyglet.window.key.F:
106
+ self.toggle_fullscreen()
107
+ elif symbol == pyglet.window.key.H or symbol == pyglet.window.key.QUESTION:
108
+ Thread(target=display_help, daemon=True).start()
109
+
110
+ if symbol in [
111
+ pyglet.window.key.LEFT,
112
+ pyglet.window.key.RIGHT,
113
+ pyglet.window.key.DOWN,
114
+ pyglet.window.key.UP,
115
+ ]:
116
+ if ctrl or shift:
117
+ super().on_key_press(symbol, modifiers)
118
+ return
119
+ mlen = len(meshes)
120
+ if symbol == pyglet.window.key.LEFT:
121
+ if mlen > 1:
122
+ view_lock.acquire()
123
+ cur_idx = (cur_idx - 1) % mlen
124
+ display_needed = True
125
+ view_lock.release()
126
+ elif symbol == pyglet.window.key.RIGHT:
127
+ if mlen > 1:
128
+ view_lock.acquire()
129
+ cur_idx = (cur_idx + 1) % mlen
130
+ display_needed = True
131
+ view_lock.release()
132
+ elif symbol == pyglet.window.key.DOWN:
133
+ pass
134
+ elif symbol == pyglet.window.key.UP:
135
+ pass
136
+
137
+
138
+ def _queue_handler():
139
+ global display_needed, meshes, titles, cur_idx
140
+ while True:
141
+ data = server.viewQueue.get()
142
+ view_lock.acquire()
143
+ try:
144
+ obj = json.loads(data)
145
+ if "clear" in obj:
146
+ meshes = []
147
+ titles = []
148
+ display_needed = True
149
+ cur_idx = 0
150
+ else:
151
+ display_needed = True
152
+ vertices = np.array(obj["vertices"], np.float64)
153
+ faces = np.array(obj["faces"], np.int64)
154
+ mesh = trimesh.Trimesh(vertices, faces)
155
+ mesh.visual.vertex_colors = obj["color"]
156
+ meshes.append(mesh)
157
+ titles.append(obj["title"])
158
+ cur_idx = len(meshes) - 1
159
+ except Exception as err:
160
+ print("Exception:", err)
161
+ pass
162
+ view_lock.release()
163
+
164
+
165
+ server.start_server()
166
+ Thread(target=_queue_handler, daemon=True).start()
167
+
168
+
169
+ window = None
170
+
171
+ cur_mesh = None
172
+
173
+
174
+ def cb(scene: trimesh.Scene):
175
+ global display_needed, meshes, titles, cur_mesh, window
176
+ changed = False
177
+ view_lock.acquire()
178
+ if display_needed:
179
+ scene.delete_geometry(cur_mesh)
180
+ if len(meshes) > 0:
181
+ window.set_caption(
182
+ f"CAD Viewer - #{cur_idx+1} of {len(meshes)} - {titles[cur_idx]}"
183
+ )
184
+ mesh = meshes[cur_idx]
185
+ scene2 = trimesh.Scene(mesh)
186
+ window._initial_camera_transform = scene2.camera_transform
187
+ scene = window.scene = window._scene = scene2
188
+ cur_mesh = scene.add_geometry(mesh)
189
+ window.reset_view()
190
+ # window.view["ball"].scroll(-(mesh.bounds[1,1]-mesh.bounds[0,1])*0.2)
191
+ # window.scene.camera_transform = window.view["ball"].pose
192
+ else:
193
+ window.set_caption(f"CAD Viewer - #0 of 0")
194
+ changed = True
195
+ display_needed = False
196
+ view_lock.release()
197
+ return changed
198
+
199
+
200
+ def main():
201
+ global cur_mesh, window
202
+ scene = trimesh.Scene()
203
+ cur_mesh = scene.add_geometry(trimesh.creation.box((100, 100, 100)))
204
+ window = MySceneViewer(
205
+ scene,
206
+ callback=cb,
207
+ callback_period=1.0,
208
+ start_loop=False,
209
+ background=(173, 216, 230, 255),
210
+ smooth=False,
211
+ )
212
+ scene.delete_geometry(cur_mesh)
213
+ window.set_caption(f"CAD Viewer - #0 of 0")
214
+
215
+ pyglet.app.run()
@@ -0,0 +1,47 @@
1
+ from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
2
+ from threading import Thread
3
+ from queue import Queue
4
+
5
+ addr = "127.0.0.1"
6
+ port = 8037
7
+
8
+ viewQueue = Queue()
9
+
10
+
11
+ class CADViewerHandler(BaseHTTPRequestHandler):
12
+ def do_POST(self):
13
+ len = int(self.headers["Content-Length"])
14
+ print("Content-Length:", len)
15
+ data = self.rfile.read(len)
16
+ self.send_response(200)
17
+ self.end_headers()
18
+ try:
19
+ self.wfile.write(bytes("got it", "utf-8"))
20
+ except:
21
+ print("Exception writing response")
22
+ pass
23
+ viewQueue.put(data)
24
+
25
+
26
+ def start_server():
27
+ Thread(target=_start_server, daemon=True).start()
28
+
29
+
30
+ def _start_server():
31
+ print("Viewer server started on (", addr, port, ")")
32
+ httpd = ThreadingHTTPServer((addr, port), CADViewerHandler)
33
+ try:
34
+ httpd.serve_forever()
35
+ except KeyboardInterrupt:
36
+ pass
37
+ httpd.server_close()
38
+
39
+
40
+ if __name__ == "__main__":
41
+ start_server()
42
+ print("^C to exit.")
43
+ while True:
44
+ try:
45
+ input("")
46
+ except KeyboardInterrupt:
47
+ break
@@ -0,0 +1,27 @@
1
+ [build-system]
2
+ requires = ["flit_core >=3.2,<4"]
3
+ build-backend = "flit_core.buildapi"
4
+
5
+ [project]
6
+ name = "piecad_viewer"
7
+ description = '"Viewer for Easy as Pie" CAD (Piecad)'
8
+ version = "1.0.0"
9
+ authors = [{name = "Brian Sturgill", email = "codersnext@gmail.com"}]
10
+ license = {file = "LICENSE"}
11
+ classifiers = ["License :: OSI Approved :: MIT License"]
12
+ dependencies = [
13
+ "pyglet == 1.5.31",
14
+ "trimesh >= 1.26.4",
15
+ "glooey >= 0.3.6",
16
+ "scipy >= 1.15.2"
17
+ ]
18
+
19
+ [project.scripts]
20
+ piecad-viewer = "piecad_viewer:main"
21
+
22
+ [project.urls]
23
+ Home = "https://github.com/briansturgill/Piecad-Viewer"
24
+
25
+ [tool.flit.sdist]
26
+ include = []
27
+ exclude = []
@@ -0,0 +1,6 @@
1
+ rm -rf lib lib64 include bin share pyvenv.cfg __pycache__
2
+ /usr/bin/python -m venv .
3
+ . bin/activate
4
+ pip install trimesh 'pyglet<2' glooey scipy
5
+ #sudo apt install libglu1-mesa python3-tk
6
+ git checkout .gitignore