ocp-vscode 1.2.2__tar.gz → 2.0.2__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.
Files changed (23) hide show
  1. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/PKG-INFO +2 -2
  2. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/README.md +19 -15
  3. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/__init__.py +23 -0
  4. ocp_vscode-2.0.2/ocp_vscode/backend.py +410 -0
  5. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/comms.py +54 -5
  6. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/config.py +8 -2
  7. ocp_vscode-2.0.2/ocp_vscode/interactive.py +9 -0
  8. ocp_vscode-2.0.2/ocp_vscode/persistence.py +123 -0
  9. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/show.py +67 -44
  10. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/PKG-INFO +2 -2
  11. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/SOURCES.txt +6 -1
  12. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/requires.txt +1 -1
  13. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/setup.cfg +1 -1
  14. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/setup.py +3 -3
  15. ocp_vscode-2.0.2/test/test_for_backend.py +19 -0
  16. ocp_vscode-2.0.2/test/testextension.py +58 -0
  17. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/LICENSE +0 -0
  18. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/animation.py +0 -0
  19. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/colors.py +0 -0
  20. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/finder.py +0 -0
  21. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/dependency_links.txt +0 -0
  22. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/not-zip-safe +0 -0
  23. {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ocp_vscode
3
- Version: 1.2.2
3
+ Version: 2.0.2
4
4
  Summary: OCP CAD Viewer for VSCode
5
5
  Home-page: https://github.com/bernhard-42/vscode-ocp-cad-viewer
6
6
  Author: Bernhard Walter
@@ -18,4 +18,4 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Requires-Python: >=3.9
19
19
  License-File: LICENSE
20
20
 
21
- An extension to show OCP cad CAD objects (CadQuery, build123d) in VS Code via pythreejs
21
+ An extension to show OCP cad CAD objects (CadQuery, build123d) in VS Code via threejs
@@ -6,7 +6,7 @@ _OCP CAD Viewer_ for VS Code is an extension to show [CadQuery](https://github.c
6
6
 
7
7
  ### Prerequisites
8
8
 
9
- - A fairly recent version of Microsoft VS Code, e.g. 1.76.0 or newer
9
+ - A fairly recent version of Microsoft VS Code, e.g. 1.84.0 or newer
10
10
  - The [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) installed in VS Code
11
11
  - Necessary tools:
12
12
  - `python` and `pip` available in the Python enviroment that will be used for CAD development
@@ -19,14 +19,9 @@ _OCP CAD Viewer_ for VS Code is an extension to show [CadQuery](https://github.c
19
19
  - For VSCodium, the extension is not available in the VS code market place. You need to download the the vsix file from the [release folder](https://github.com/bernhard-42/vscode-ocp-cad-viewer/releases) and install it manually.
20
20
  - Currently, on a Silicon Mac (ARM CPU), _OCP_ and _CadQuery_ can only be installed via `mamba` and Python 3.9 or 3.10. Prepare an environment with `mamba create -n code_cad python=3.9` or `mamba create -n code_cad python=3.10`.
21
21
 
22
- ### Breaking changes from v1.0.0
23
-
24
- - IPython and the ipython extensions are not supported any more out of the box. Instead the Microsoft's Jupyter extension with ipykernel is supported. If you have the installation configs in your local VS Code settings.json file, you might want to remove the ipython installation commands.
25
- - For the color maps, `CM` is replaced by `ColorMap` (to resolve the conflict with build123d `CM`)
26
-
27
22
  ### Installation
28
23
 
29
- 1. Open the VS Code Marketplace, and search and install _OCP CAD Viewer 1.2.2_.
24
+ 1. Open the VS Code Marketplace, and search and install _OCP CAD Viewer 2.0.2_.
30
25
 
31
26
  Afterwards the OCP viewer is available in the VS Code sidebar:
32
27
 
@@ -176,14 +171,23 @@ You can also use "Library Manager" in the _OCP CAD Viewer_ sidebar to manage the
176
171
 
177
172
  ## Changes
178
173
 
179
- v1.2.2
174
+ v2.0.2
175
+
176
+ - Fix .ocp_vscode detection on Windows
177
+ - Fix showing ShapeList[Vector]
178
+ - Viewer now starts when VS Code opens with a build123d/cadquery Python file
179
+
180
+ v2.0.1
181
+
182
+ - Introduce a workspace configuration `initialPort` for OCP CAD Viewer
183
+ - Change the warning about used port to an auto vanishing info
184
+
185
+ v2.0.0
180
186
 
181
- - Replace the boolean values for `reset_camera` with the `Camera` enum; `reset_camera` now supports `Camera.RESET` (works like `True` before), `Camera.CENTER` (works like `False` before) and `Camera.KEEP` (additionally keeps the pan location). See best practices for details.
182
- - Replace the values for `collapse` with the `Collapse` enum: `Collapse.ALL` (was `"C"`), `Collapse.None` (was `"E"`), `Collapse.LEAVES` (was `"1"` or `1`) and `Collapse.Root` (was `"R"`)
183
- - Added a button to toggle the output panel for OCP CAD Viewer
184
- - Visual debug is on by default now (workspace setting `WatchByDefault` to `true`)
185
- - Changed behavior of ` show` during debug session: `show` will be executed, however, visual debug step omitted
186
- - Do not show Jupyter variables `_`, `__`, `_1`, `_2`, ... in `show_all`
187
- - Fix an error where the orientation marker was partly or fully moved outside its view due to panning of the object ([vscode-ocp-cad-viewer issue #22](https://github.com/bernhard-42/vscode-ocp-cad-viewer/issues/22))
187
+ - Introduce **measure mode**. Use `set_defaults(measure_tools=True)` or `show(obj, measure_tools=True)` or the global workspace confiv of OCP CAD Viewer to turn it on. This release added a Python backend process that communicates with the viewer for providing correct BRep measurement info.
188
+ - **Autostart** viewer: When opening or saving a Python file that includes `import cadquery`, `import build123d`, `from cadquery import` or `from build123d import`, the viewer gets started if it is not already running. Use `OcpCadViewer.autostart` configuration of workspace config to turn this feature off.
189
+ - **Environment variable OCP_PORT** is supported. It will be used by the viewer and the `show*` clients as the port for OCP CAD Viewer. This variable can be set on the command line or in `launch.json` (`"env": {"OCP_PORT": "3999"}`).
190
+ - **Modifier keys can now be remapped** `key={"shift": "shiftKey", "ctrl": "ctrlKey", "meta": "metaKey"}`. Valid keys are `shift`, `ctrl` and `meta`. Valid values are `shiftKey`, `ctrlKey`, `metaKey` (command on Mac and Windows on Windows) and `altKey` (option on Mac and Alt on Windows).
191
+ - `CAMERA.KEEP` and `CAMERA.CENTER` now **persist the viewer across execution sessions**. If you want to reset at every beginning, use `show(objs, reset_camer=Camera.RESET)` as the first show command.
188
192
 
189
193
  full change log see [Changes.md](./Changes.md)
@@ -13,10 +13,33 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
+ __version__ = "2.0.0"
16
17
 
18
+ import json
17
19
  from .show import *
18
20
  from .config import *
19
21
  from .comms import *
20
22
 
21
23
  from .colors import *
22
24
  from .animation import Animation
25
+
26
+ from pathlib import Path
27
+ from os import environ
28
+
29
+ try:
30
+ port = int(environ.get("OCP_PORT", "0"))
31
+ if port > 0:
32
+ set_port(port)
33
+ else:
34
+ current_path = Path.cwd()
35
+ for path in current_path.parents:
36
+ file_path = path / ".ocp_vscode"
37
+ if file_path.exists():
38
+ with open(file_path, "r") as f:
39
+ port = json.load(f)["port"]
40
+ set_port(port)
41
+ break
42
+ except:
43
+ pass
44
+
45
+ del environ
@@ -0,0 +1,410 @@
1
+ from dataclasses import dataclass, asdict, fields
2
+ import argparse
3
+ import sys
4
+ import traceback
5
+ import base64
6
+ from ocp_vscode.comms import listener, MessageType, send_response
7
+ from build123d import (
8
+ Axis,
9
+ CenterOf,
10
+ GeomType,
11
+ Location,
12
+ Plane,
13
+ Vector,
14
+ Vertex,
15
+ Edge,
16
+ Face,
17
+ Solid,
18
+ Shape,
19
+ Compound,
20
+ Location,
21
+ )
22
+ from build123d.topology import downcast
23
+ from ocp_tessellate.tessellator import (
24
+ face_mapper,
25
+ edge_mapper,
26
+ vertex_mapper,
27
+ get_faces,
28
+ get_edges,
29
+ get_vertices,
30
+ )
31
+ from ocp_tessellate.ocp_utils import make_compound, deserialize, tq_to_loc
32
+ from ocp_tessellate.trace import Trace, dump_face, dump_edge, dump_vertex
33
+
34
+
35
+ class SelectedCenterInfo:
36
+ """
37
+ Stores the information message about what as been used as center for the measurement
38
+ """
39
+
40
+ vertex = "Reference point has been taken as vertex location"
41
+ circular = "Reference point has been taken as the center of the circle or ellipse"
42
+ geom = "Reference point has been taken as the center of the geometry"
43
+ cylinder = "Reference point has been taken as the center of the cylinder"
44
+
45
+
46
+ def print_to_stdout(*msg):
47
+ """
48
+ Write the given message to the stdout
49
+ """
50
+ print(*msg, flush=True, file=sys.stdout)
51
+
52
+
53
+ def error_handler(func):
54
+ def wrapper(*args, **kwargs):
55
+ try:
56
+ func(*args, **kwargs)
57
+ except Exception as ex:
58
+ print_to_stdout(ex)
59
+ traceback.print_exception(*sys.exc_info(), file=sys.stdout)
60
+
61
+ return wrapper
62
+
63
+
64
+ @dataclass
65
+ class Tool:
66
+ Distance = "DistanceMeasurement"
67
+ Properties = "PropertiesMeasurement"
68
+ Angle = "AngleMeasurement"
69
+
70
+
71
+ def set_precision(instance, decimals=2):
72
+ """
73
+ Set the precision of all float fields of the dataclass to the given number of decimals
74
+ """
75
+ for field in fields(instance):
76
+ if field.type == float:
77
+ value = getattr(instance, field.name)
78
+ if value is not None:
79
+ setattr(instance, field.name, round(value, decimals))
80
+ elif isinstance(getattr(instance, field.name), tuple):
81
+ # Handle tuple fields
82
+ old_tuple = getattr(instance, field.name)
83
+ new_tuple = tuple(
84
+ round(elem, decimals) if isinstance(elem, float) else elem
85
+ for elem in old_tuple
86
+ )
87
+ setattr(instance, field.name, new_tuple)
88
+
89
+
90
+ @dataclass
91
+ class Response:
92
+ type: str = "backend_response"
93
+
94
+
95
+ @dataclass
96
+ class MeasureReponse(Response):
97
+ center_info: str = "" # a string telling the frontend how the reference points used for the measurement were chosen
98
+ subtype: str = "tool_response"
99
+
100
+
101
+ @dataclass
102
+ class DistanceResponse(MeasureReponse):
103
+ tool_type: Tool = Tool.Distance
104
+ point1: tuple = None
105
+ point2: tuple = None
106
+ distance: float = None
107
+
108
+
109
+ @dataclass
110
+ class PropertiesResponse(MeasureReponse):
111
+ tool_type: Tool = Tool.Properties
112
+ center: tuple = None
113
+ vertex_coords: tuple = None
114
+ length: float = None
115
+ width: float = None
116
+ area: float = None
117
+ volume: float = None
118
+ radius: float = None
119
+ geom_type: str = None
120
+
121
+
122
+ @dataclass
123
+ class AngleResponse(MeasureReponse):
124
+ tool_type: Tool = Tool.Angle
125
+ angle: float = None
126
+ point1: tuple = None
127
+ point2: tuple = None
128
+
129
+
130
+ class ViewerBackend:
131
+ """
132
+ Represents the backend of the viewer, it listens to the websocket and handles the events
133
+ It's job is to send responses to the vscode extension that goes through the three cad viewer view.
134
+ The reponses holds all the data needed to display the measurements.
135
+ """
136
+
137
+ def __init__(self, port: int) -> None:
138
+ self.port = port
139
+ self.model = None
140
+ self.activated_tool = None
141
+ self.filter_type = "none" # The current active selection filter
142
+
143
+ def start(self):
144
+ """
145
+ Start the backend
146
+ """
147
+ print("Viewer backend started")
148
+ listener(self.handle_event)()
149
+
150
+ @error_handler
151
+ def handle_event(self, message, event_type: MessageType):
152
+ """
153
+ Handle the event received from the websocket
154
+ Dispatch the event to the appropriate handler
155
+ """
156
+ if event_type == MessageType.data:
157
+ self.load_model(message)
158
+ elif event_type == MessageType.updates:
159
+ changes = message
160
+
161
+ if "activeTool" in changes:
162
+ active_tool = changes.get("activeTool")
163
+
164
+ if active_tool != "None":
165
+ self.activated_tool = active_tool
166
+ else:
167
+ self.activated_tool = None
168
+
169
+ if self.activated_tool is not None:
170
+ self.handle_activated_tool(changes)
171
+
172
+ def handle_activated_tool(self, changes):
173
+ """
174
+ Handle the activated tool, there is a special behavior for each tool
175
+ """
176
+ if not "selectedShapeIDs" in changes:
177
+ return
178
+
179
+ selectedObjs = changes["selectedShapeIDs"]
180
+ if self.activated_tool == Tool.Distance and len(selectedObjs) == 2:
181
+ shape_id1 = changes["selectedShapeIDs"][0]
182
+ shape_id2 = changes["selectedShapeIDs"][1]
183
+ self.handle_distance(shape_id1, shape_id2)
184
+
185
+ elif self.activated_tool == Tool.Properties and len(selectedObjs) == 1:
186
+ shape_id = changes["selectedShapeIDs"][0]
187
+ self.handle_properties(shape_id)
188
+
189
+ elif self.activated_tool == Tool.Angle and len(selectedObjs) == 2:
190
+ shape_id1 = changes["selectedShapeIDs"][0]
191
+ shape_id2 = changes["selectedShapeIDs"][1]
192
+ self.handle_angle(shape_id1, shape_id2)
193
+
194
+ def load_model(self, raw_model):
195
+ """Read the transfered model from websocket"""
196
+
197
+ def walk(model, trace):
198
+ for v in model["parts"]:
199
+ if v.get("parts") is not None:
200
+ walk(v, trace)
201
+ else:
202
+ id = v["id"]
203
+ loc = (
204
+ Location().wrapped if v["loc"] is None else tq_to_loc(*v["loc"])
205
+ )
206
+ shape = [
207
+ deserialize(base64.b64decode(s.encode("utf-8")))
208
+ for s in v["shape"]
209
+ ]
210
+ compound = make_compound(shape) if len(shape) > 1 else shape[0]
211
+ self.model[id] = Compound(compound.Moved(loc))
212
+ faces = get_faces(compound)
213
+ for i, face in enumerate(faces):
214
+ trace.face(f"{id}/faces/faces_{i}", face)
215
+
216
+ self.model[f"{id}/faces/faces_{i}"] = Face(face.Moved(loc))
217
+ edges = get_edges(compound)
218
+ for i, edge in enumerate(edges):
219
+ trace.edge(f"{id}/edges/edges_{i}", edge)
220
+
221
+ self.model[f"{id}/edges/edges_{i}"] = (
222
+ Edge(edge) if loc is None else Edge(edge.Moved(loc))
223
+ )
224
+ vertices = get_vertices(compound)
225
+ for i, vertex in enumerate(vertices):
226
+ trace.vertex(f"{id}/vertices/vertex{i}", vertex)
227
+
228
+ self.model[f"{id}/vertices/vertices{i}"] = (
229
+ Vertex(vertex)
230
+ if loc is None
231
+ else Vertex(downcast(vertex.Moved(loc)))
232
+ )
233
+
234
+ self.model = {}
235
+ trace = Trace("ocp-vscode-backend.log")
236
+ walk(raw_model, trace)
237
+ trace.close()
238
+
239
+ def handle_properties(self, shape_id):
240
+ """
241
+ Request the properties of the object with the given id
242
+ """
243
+ print_to_stdout(f"Identifier received '{shape_id}'")
244
+
245
+ shape = self.model[shape_id]
246
+
247
+ response = PropertiesResponse()
248
+
249
+ if isinstance(shape, Vertex):
250
+ response.vertex_coords = shape.to_tuple()
251
+
252
+ elif isinstance(shape, Edge):
253
+ response.radius = shape.radius if shape.geom_type() in ["CIRCLE"] else None
254
+ response.length = shape.length
255
+
256
+ elif isinstance(shape, Face):
257
+ if shape.geom_type() == "CYLINDER":
258
+ circle = shape.edges().filter_by(GeomType.CIRCLE).first
259
+ response.radius = circle.radius
260
+
261
+ response.length = shape.length
262
+ response.width = shape.width
263
+ response.area = shape.area
264
+
265
+ elif isinstance(shape, (Solid, Compound)):
266
+ response.volume = shape.volume
267
+
268
+ geom_type = shape.geom_type().capitalize()
269
+ response.geom_type = geom_type if geom_type != "Vertex" else None
270
+ center, info = self.get_center(shape, False)
271
+ response.center = center.to_tuple()
272
+ response.vertex_coords = response.center
273
+ response.center_info = f"{shape_id} : {info}"
274
+
275
+ set_precision(response)
276
+
277
+ send_response(asdict(response), self.port)
278
+ print_to_stdout(f"Data sent {response}")
279
+
280
+ def handle_angle(self, id1, id2):
281
+ """
282
+ Request the angle between the two objects that have the given ids
283
+ """
284
+ print_to_stdout(f"Identifiers received '{id1}', '{id2}'")
285
+
286
+ shape1: Shape = self.model[id1]
287
+ shape2: Shape = self.model[id2]
288
+ first = (
289
+ Plane(shape1)
290
+ if isinstance(shape1, Face)
291
+ else Plane(shape1 @ 0, z_dir=shape1.normal())
292
+ if isinstance(shape1, Edge) and shape1.geom_type() in ["CIRCLE", "ELLIPSE"]
293
+ else shape1 % 0
294
+ )
295
+ second = (
296
+ Plane(shape2)
297
+ if isinstance(shape2, Face)
298
+ else Plane(shape2 @ 0, z_dir=shape2.normal())
299
+ if isinstance(shape2, Edge) and shape2.geom_type() in ["CIRCLE", "ELLIPSE"]
300
+ else shape2 % 0
301
+ )
302
+ if type(first) == type(second) == Plane:
303
+ angle = first.z_dir.get_angle(second.z_dir)
304
+ elif type(first) == type(second) == Vector:
305
+ angle = first.get_angle(second)
306
+ else:
307
+ vector = first if isinstance(first, Vector) else second
308
+ plane = first if isinstance(first, Plane) else second
309
+
310
+ angle = 90 - plane.z_dir.get_angle(vector)
311
+ angle = abs(angle)
312
+ point1, info1 = self.get_center(shape1, True)
313
+ point2, info2 = self.get_center(shape2, True)
314
+ center_info = f"{id1} : {info1}\n{id2} : {info2}"
315
+ response = AngleResponse(
316
+ center_info=center_info,
317
+ angle=angle,
318
+ point1=point1.to_tuple(),
319
+ point2=point2.to_tuple(),
320
+ )
321
+ set_precision(response)
322
+ send_response(asdict(response), self.port)
323
+ print_to_stdout(f"Data sent {response}")
324
+
325
+ def get_center(
326
+ self, shape: Shape, for_distance=True
327
+ ) -> tuple[Vector, SelectedCenterInfo]:
328
+ """
329
+ Returns the center vector of the given shape
330
+ Center of the shape depends on the type of the shape and the tool used
331
+ For instance, circle edge center will be on the edge for properties tool
332
+ but at the center of the circle for distance tool
333
+ """
334
+ if isinstance(shape, Vertex):
335
+ return shape.center(), SelectedCenterInfo.vertex
336
+ elif isinstance(shape, Edge):
337
+ if shape.geom_type() in [
338
+ GeomType.CIRCLE,
339
+ GeomType.ELLIPSE,
340
+ "CIRCLE",
341
+ "ELLIPSE",
342
+ ]:
343
+ if for_distance:
344
+ return shape.arc_center, SelectedCenterInfo.circular
345
+ else:
346
+ return shape.center(), SelectedCenterInfo.geom
347
+
348
+ elif isinstance(shape, Face):
349
+ if shape.geom_type() in [GeomType.CYLINDER, "CYLINDER"]:
350
+ if not for_distance:
351
+ return shape.center(), SelectedCenterInfo.geom
352
+
353
+ extremity_edges = shape.edges().filter_by(GeomType.CIRCLE)
354
+ if len(extremity_edges) == 2:
355
+ return (
356
+ extremity_edges.first.arc_center
357
+ - (
358
+ extremity_edges.first.arc_center
359
+ - extremity_edges.last.arc_center
360
+ )
361
+ / 2,
362
+ SelectedCenterInfo.cylinder,
363
+ )
364
+ else:
365
+ try:
366
+ return (
367
+ extremity_edges.first.arc_center,
368
+ SelectedCenterInfo.cylinder,
369
+ )
370
+ except IndexError:
371
+ # cylinder might have bspline extremity edges hence the list would be empty
372
+ # in that case we default to geom center
373
+ pass
374
+
375
+ return shape.center(), SelectedCenterInfo.geom
376
+
377
+ def handle_distance(self, id1, id2):
378
+ """
379
+ Request the distance between the two objects that have the given ids
380
+ """
381
+ print_to_stdout(f"Identifiers received '{id1}', '{id2}'")
382
+
383
+ shape1: Shape = self.model[id1]
384
+ shape2: Shape = self.model[id2]
385
+ p1, info1 = self.get_center(shape1)
386
+ p2, info2 = self.get_center(shape2)
387
+ center_info = f"{id1} : {info1}\n{id2} : {info2}"
388
+ dist = (p2 - p1).length
389
+ response = DistanceResponse(
390
+ center_info=center_info,
391
+ point1=p1.to_tuple(),
392
+ point2=p2.to_tuple(),
393
+ distance=dist,
394
+ )
395
+ set_precision(response)
396
+ send_response(asdict(response), self.port)
397
+ print_to_stdout(f"Data sent {response}")
398
+
399
+
400
+ if __name__ == "__main__":
401
+ parser = argparse.ArgumentParser("OCP Viewer Backend")
402
+ parser.add_argument(
403
+ "--port", type=int, required=True, help="Port the viewer listens to"
404
+ )
405
+ args = parser.parse_args()
406
+ backend = ViewerBackend(args.port)
407
+ try:
408
+ backend.start()
409
+ except Exception as ex:
410
+ print(ex)
@@ -1,7 +1,14 @@
1
+ import base64
1
2
  import enum
2
3
  from websockets.sync.client import connect
3
4
  import orjson as json
4
5
  from ocp_tessellate.utils import Timer
6
+ from ocp_tessellate.ocp_utils import (
7
+ is_topods_shape,
8
+ is_toploc_location,
9
+ serialize,
10
+ loc_to_tq,
11
+ )
5
12
 
6
13
  CMD_URL = "ws://127.0.0.1"
7
14
  CMD_PORT = 3939
@@ -16,9 +23,28 @@ class MessageType(enum.IntEnum):
16
23
  command = 2
17
24
  updates = 3
18
25
  listen = 4
26
+ backend = 5
27
+ backend_response = 6
28
+ config = 7
19
29
 
20
30
 
21
- __all__ = ["send_data", "send_command", "set_port", "get_port", "listener"]
31
+ __all__ = [
32
+ "send_data",
33
+ "send_command",
34
+ "send_response",
35
+ "set_port",
36
+ "get_port",
37
+ "listener",
38
+ ]
39
+
40
+
41
+ def default(obj):
42
+ if is_topods_shape(obj):
43
+ return base64.b64encode(serialize(obj)).decode("utf-8")
44
+ elif is_toploc_location(obj):
45
+ return loc_to_tq(obj)
46
+ else:
47
+ raise TypeError(f"Object of type {type(obj)} is not JSON serializable")
22
48
 
23
49
 
24
50
  def get_port():
@@ -35,13 +61,21 @@ def _send(data, message_type, port=None, timeit=False):
35
61
  port = CMD_PORT
36
62
  try:
37
63
  with Timer(timeit, "", "json dumps", 1):
38
- j = json.dumps(data)
64
+ j = json.dumps(data, default=default)
39
65
  if message_type == MessageType.command:
40
66
  j = b"C:" + j
41
67
  elif message_type == MessageType.data:
42
68
  j = b"D:" + j
43
-
44
- with Timer(timeit, "", "websocket send", 1):
69
+ elif message_type == MessageType.listen:
70
+ j = b"L:" + j
71
+ elif message_type == MessageType.backend:
72
+ j = b"B:" + j
73
+ elif message_type == MessageType.backend_response:
74
+ j = b"R:" + j
75
+ elif message_type == MessageType.config:
76
+ j = b"S:" + j
77
+
78
+ with Timer(timeit, "", f"websocket send {len(j)/1024/1024:.3f} MB", 1):
45
79
  ws = connect(f"{CMD_URL}:{port}")
46
80
  ws.send(j)
47
81
 
@@ -68,10 +102,22 @@ def send_data(data, port=None, timeit=False):
68
102
  return _send(data, MessageType.data, port, timeit)
69
103
 
70
104
 
105
+ def send_config(config, port=None, timeit=False):
106
+ return _send(config, MessageType.config, port, timeit)
107
+
108
+
71
109
  def send_command(data, port=None, timeit=False):
72
110
  return _send(data, MessageType.command, port, timeit)
73
111
 
74
112
 
113
+ def send_backend(data, port=None, timeit=False):
114
+ return _send(data, MessageType.backend, port, timeit)
115
+
116
+
117
+ def send_response(data, port=None, timeit=False):
118
+ return _send(data, MessageType.backend_response, port, timeit)
119
+
120
+
75
121
  #
76
122
  # Receive data from the viewer
77
123
  #
@@ -84,7 +130,7 @@ def send_command(data, port=None, timeit=False):
84
130
  def listener(callback):
85
131
  def _listen():
86
132
  LAST_CONFIG = {}
87
- with connect(f"{CMD_URL}:{CMD_PORT}") as websocket:
133
+ with connect(f"{CMD_URL}:{CMD_PORT}", max_size=2**28) as websocket:
88
134
  websocket.send(b"L:register")
89
135
  while True:
90
136
  try:
@@ -93,6 +139,9 @@ def listener(callback):
93
139
  continue
94
140
 
95
141
  message = json.loads(message)
142
+ if "model" in message.keys():
143
+ callback(message["model"], MessageType.data)
144
+
96
145
  if message.get("command") == "status":
97
146
  changes = message["text"]
98
147
  new_changes = {}
@@ -16,7 +16,7 @@
16
16
 
17
17
  from enum import Enum
18
18
 
19
- from .comms import send_command, send_data, get_port
19
+ from .comms import send_command, send_config, get_port
20
20
 
21
21
  __all__ = [
22
22
  "workspace_config",
@@ -65,6 +65,7 @@ CONFIG_WORKSPACE_KEYS = CONFIG_UI_KEYS + [
65
65
  "collapse",
66
66
  "dark",
67
67
  "glass",
68
+ "measure_tools",
68
69
  "orbit_control",
69
70
  "ticks",
70
71
  "tools",
@@ -87,6 +88,8 @@ CONFIG_WORKSPACE_KEYS = CONFIG_UI_KEYS + [
87
88
  "default_vertexcolor",
88
89
  "default_opacity",
89
90
  "deviation",
91
+ "measure_tools",
92
+ "modifier_keys",
90
93
  ]
91
94
 
92
95
  CONFIG_CONTROL_KEYS = [
@@ -175,7 +178,7 @@ def set_viewer_config(
175
178
  "type": "ui",
176
179
  "config": config,
177
180
  }
178
- send_data(data)
181
+ send_config(data)
179
182
 
180
183
 
181
184
  def get_default(key):
@@ -189,6 +192,7 @@ def get_defaults():
189
192
  def set_defaults(
190
193
  glass=None,
191
194
  tools=None,
195
+ measure_tools=None,
192
196
  tree_width=None,
193
197
  axes=None,
194
198
  axes0=None,
@@ -230,6 +234,7 @@ def set_defaults(
230
234
  - UI
231
235
  glass: Use glass mode where tree is an overlay over the cad object (default=False)
232
236
  tools: Show tools (default=True)
237
+ measure_tools: Show measure tools (default=False)
233
238
  tree_width: Width of the object tree (default=240)
234
239
 
235
240
  - Viewer
@@ -368,6 +373,7 @@ def combined_config(port=None, use_status=True):
368
373
  wspace_config["ortho"] = False
369
374
  wspace_config["transparent"] = False
370
375
  wspace_config["black_edges"] = False
376
+ wspace_config["measure_tools"] = False
371
377
 
372
378
  wspace_config.update(DEFAULTS)
373
379
  if use_status:
@@ -0,0 +1,9 @@
1
+ # In this module I want to make a function that will be responsible
2
+ #for holding references to the original shapes and the converted ones
3
+ #as well as listening to commands from the viewer and sending back data
4
+ #depending on what the command
5
+
6
+ from threading import Thread
7
+
8
+ def start_interactive_session(python_objs, tcv_objs):
9
+
@@ -0,0 +1,123 @@
1
+ from OCP.BinTools import BinTools
2
+ from OCP.TopoDS import (
3
+ TopoDS_Shape,
4
+ TopoDS_Compound,
5
+ TopoDS_CompSolid,
6
+ TopoDS_Solid,
7
+ TopoDS_Shell,
8
+ TopoDS_Face,
9
+ TopoDS_Wire,
10
+ TopoDS_Edge,
11
+ TopoDS_Vertex,
12
+ )
13
+ from OCP.TopLoc import TopLoc_Location
14
+ from OCP.gp import gp_Trsf, gp_Quaternion, gp_Vec
15
+ import io
16
+ import copyreg
17
+ from build123d.topology import downcast
18
+ import struct
19
+
20
+
21
+ def serialize_shape(shape: TopoDS_Shape):
22
+ """
23
+ Serialize a OCP shape, this method can be used to provide a custom serialization algo for pickle
24
+ """
25
+ if shape is None:
26
+ return None
27
+
28
+ bio = io.BytesIO()
29
+ BinTools.Write_s(shape, bio)
30
+ buffer = bio.getvalue()
31
+ return buffer
32
+
33
+
34
+ def deserialize_shape(buffer: bytes):
35
+ """
36
+ This does the opposite as serialize, it construct a TopoDS_Shape from bytes.
37
+ """
38
+ if buffer is None:
39
+ return None
40
+
41
+ shape = TopoDS_Shape()
42
+ bio = io.BytesIO(buffer)
43
+ BinTools.Read_s(shape, bio)
44
+ return downcast(shape)
45
+
46
+
47
+ def serialize_location(location: TopLoc_Location):
48
+ """
49
+ Serialize a OCP location, this method can be used to provide a custom serialization algo for pickle
50
+ """
51
+ if location is None:
52
+ return None
53
+ transfo = location.Transformation()
54
+ translation = transfo.TranslationPart()
55
+ rotation = transfo.GetRotation()
56
+ # convert floats in bytes
57
+ translation_bytes = bytearray()
58
+ for i in range(1, 4):
59
+ translation_bytes.extend(struct.pack("f", translation.Coord(i)))
60
+ rotation_bytes = bytearray()
61
+ rotation_bytes.extend(struct.pack("f", rotation.X()))
62
+ rotation_bytes.extend(struct.pack("f", rotation.Y()))
63
+ rotation_bytes.extend(struct.pack("f", rotation.Z()))
64
+ rotation_bytes.extend(struct.pack("f", rotation.W()))
65
+
66
+ buffer = bytearray()
67
+ buffer.extend(translation_bytes)
68
+ buffer.extend(rotation_bytes)
69
+ return buffer
70
+
71
+
72
+ def deserialize_location(buffer: bytes):
73
+ """
74
+ This does the opposite as serialize, it construct a TopLoc_Location from bytes.
75
+ """
76
+ if buffer is None:
77
+ return None
78
+
79
+ # Makes 4 bytes chunks (floats are 4 bytes long)
80
+ chunks = iter(struct.iter_unpack("f", buffer))
81
+ translation = gp_Vec(
82
+ next(chunks)[0],
83
+ next(chunks)[0],
84
+ next(chunks)[0],
85
+ )
86
+
87
+ # Read the rotation bytes
88
+ rotation = gp_Quaternion(
89
+ next(chunks)[0],
90
+ next(chunks)[0],
91
+ next(chunks)[0],
92
+ next(chunks)[0],
93
+ )
94
+
95
+ # Create the TopLoc_Location object
96
+ transfo = gp_Trsf()
97
+ transfo.SetTransformation(rotation, translation)
98
+
99
+ return TopLoc_Location(transfo)
100
+
101
+
102
+ def reduce_shape(shape: TopoDS_Shape):
103
+ """Special function used by pickle to serialize or deserialize OCP Shapes objects"""
104
+ return (deserialize_shape, (serialize_shape(shape),))
105
+
106
+
107
+ def reduce_location(location: TopLoc_Location):
108
+ """Special function used by pickle to serialize or deserialize OCP Location objects"""
109
+ return (deserialize_location, (serialize_location(location),))
110
+
111
+
112
+ def modify_copyreg():
113
+ """Modify the copyreg so that pickle knows what to look for when it tries to pickle an OCP Shape"""
114
+ copyreg.pickle(TopoDS_Shape, reduce_shape)
115
+ copyreg.pickle(TopoDS_Compound, reduce_shape)
116
+ copyreg.pickle(TopoDS_CompSolid, reduce_shape)
117
+ copyreg.pickle(TopoDS_Solid, reduce_shape)
118
+ copyreg.pickle(TopoDS_Shell, reduce_shape)
119
+ copyreg.pickle(TopoDS_Face, reduce_shape)
120
+ copyreg.pickle(TopoDS_Wire, reduce_shape)
121
+ copyreg.pickle(TopoDS_Edge, reduce_shape)
122
+ copyreg.pickle(TopoDS_Vertex, reduce_shape)
123
+ copyreg.pickle(TopLoc_Location, reduce_location)
@@ -14,7 +14,7 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  import re
17
- import sys
17
+
18
18
  from ocp_tessellate import PartGroup
19
19
  from ocp_tessellate.convert import (
20
20
  tessellate_group,
@@ -23,6 +23,7 @@ from ocp_tessellate.convert import (
23
23
  to_assembly,
24
24
  mp_get_results,
25
25
  is_topods_shape,
26
+ is_cadquery,
26
27
  is_vector,
27
28
  )
28
29
  from ocp_tessellate.utils import numpy_to_buffer_json, Timer, Color
@@ -34,7 +35,6 @@ from ocp_tessellate.ocp_utils import (
34
35
  is_cadquery_assembly,
35
36
  is_cadquery_sketch,
36
37
  is_build123d,
37
- is_build123d_assembly,
38
38
  is_toploc_location,
39
39
  )
40
40
 
@@ -46,7 +46,7 @@ from ocp_tessellate.cad_objects import (
46
46
  OCP_Part,
47
47
  OCP_Vertices,
48
48
  )
49
- from ocp_tessellate.convert import to_assembly, conv
49
+ from ocp_tessellate.convert import to_assembly
50
50
  import ocp_tessellate.convert as oc
51
51
 
52
52
  from .config import (
@@ -55,38 +55,35 @@ from .config import (
55
55
  workspace_config,
56
56
  combined_config,
57
57
  get_default,
58
- status,
59
- set_viewer_config,
60
58
  Camera,
61
59
  Collapse,
62
60
  check_deprecated,
63
61
  )
64
- from .comms import send_data, MessageType
62
+ from .comms import send_backend, send_data
65
63
  from .colors import *
66
64
 
65
+ from .persistence import modify_copyreg
66
+
67
67
  __all__ = ["show", "show_object", "reset_show", "show_all", "show_clear"]
68
68
 
69
69
  OBJECTS = {"objs": [], "names": [], "colors": [], "alphas": []}
70
70
 
71
- FIRST_CALL = True
72
71
  LAST_CALL = "other"
73
72
 
73
+ modify_copyreg()
74
+
74
75
 
75
76
  def _tessellate(
76
77
  *cad_objs, names=None, colors=None, alphas=None, progress=None, **kwargs
77
78
  ):
78
- global FIRST_CALL
79
-
80
79
  if workspace_config().get("_splash"):
81
80
  conf = combined_config(use_status=False)
81
+ reset_camera = Camera.RESET
82
82
  else:
83
83
  conf = combined_config(use_status=True)
84
- if FIRST_CALL:
85
- conf["reset_camera"] = Camera.RESET.value
86
- FIRST_CALL = False
87
- else:
88
- reset_camera = conf.get("reset_camera", Camera.RESET)
89
- conf["reset_camera"] = reset_camera.value
84
+ reset_camera = conf.get("reset_camera", Camera.RESET)
85
+
86
+ conf["reset_camera"] = reset_camera.value
90
87
 
91
88
  collapse = conf.get("collapse", Collapse.LEAVES)
92
89
  conf["collapse"] = collapse.value
@@ -171,6 +168,8 @@ def _tessellate(
171
168
  print(f"Setting {k} can only be set in VSCode config")
172
169
 
173
170
  elif v is not None:
171
+ if k == "reset_camera" and params.get("_splash") == True:
172
+ continue
174
173
  params[k] = v
175
174
 
176
175
  parallel = preset("parallel", params.get("parallel"))
@@ -189,7 +188,7 @@ def _tessellate(
189
188
  init_pool()
190
189
  keymap.reset()
191
190
 
192
- instances, shapes, states = tessellate_group(
191
+ instances, shapes, states, mapping = tessellate_group(
193
192
  part_group, params, progress, params.get("timeit")
194
193
  )
195
194
 
@@ -208,16 +207,24 @@ def _tessellate(
208
207
 
209
208
  # add global bounding box
210
209
  shapes["bb"] = bb
211
- return instances, shapes, states, params, part_group.count_shapes()
212
210
 
211
+ return instances, shapes, states, params, part_group.count_shapes(), mapping
213
212
 
214
- def _convert(*cad_objs, names=None, colors=None, alphas=None, progress=None, **kwargs):
213
+
214
+ def _convert(
215
+ *cad_objs,
216
+ names=None,
217
+ colors=None,
218
+ alphas=None,
219
+ progress=None,
220
+ **kwargs,
221
+ ):
215
222
  timeit = preset("timeit", kwargs.get("timeit"))
216
223
 
217
224
  if progress is None:
218
225
  progress = Progress([c for c in "-+c"])
219
226
 
220
- instances, shapes, states, config, count_shapes = _tessellate(
227
+ instances, shapes, states, config, count_shapes, mapping = _tessellate(
221
228
  *cad_objs,
222
229
  names=names,
223
230
  colors=colors,
@@ -225,6 +232,7 @@ def _convert(*cad_objs, names=None, colors=None, alphas=None, progress=None, **k
225
232
  progress=progress,
226
233
  **kwargs,
227
234
  )
235
+
228
236
  if config.get("dark") is not None:
229
237
  config["theme"] = "dark"
230
238
  elif config.get("orbit_control") is not None:
@@ -237,16 +245,14 @@ def _convert(*cad_objs, names=None, colors=None, alphas=None, progress=None, **k
237
245
  config["explode"] = kwargs["explode"]
238
246
 
239
247
  with Timer(timeit, "", "create data obj", 1):
240
- data = {
248
+ return {
241
249
  "data": numpy_to_buffer_json(
242
250
  dict(instances=instances, shapes=shapes, states=states)
243
251
  ),
244
252
  "type": "data",
245
253
  "config": config,
246
254
  "count": count_shapes,
247
- }
248
-
249
- return data
255
+ }, mapping
250
256
 
251
257
 
252
258
  class Progress:
@@ -265,7 +271,7 @@ def align_attrs(attr_list, length, default, tag, explode=True):
265
271
  if attr_list is None:
266
272
  return [None] * length if explode else None
267
273
  elif len(attr_list) < length:
268
- print(f"Too view {tag}, using defaults to fill")
274
+ print(f"Too few {tag}, using defaults to fill")
269
275
  return list(attr_list) + [default] * (length - len(attr_list))
270
276
  elif len(attr_list) > length:
271
277
  print(f"Too many {tag}, trimming to length {length}")
@@ -283,6 +289,7 @@ def show(
283
289
  progress="-+c",
284
290
  glass=None,
285
291
  tools=None,
292
+ measure_tools=None,
286
293
  tree_width=None,
287
294
  axes=None,
288
295
  axes0=None,
@@ -343,6 +350,7 @@ def show(
343
350
  Valid keywords to configure the viewer (**kwargs):
344
351
  - UI
345
352
  glass: Use glass mode where tree is an overlay over the cad object (default=False)
353
+ measure_tools: Show measure tools (default=False)
346
354
  tools: Show tools (default=True)
347
355
  tree_width: Width of the object tree (default=240)
348
356
 
@@ -398,7 +406,7 @@ def show(
398
406
  render_joints: Render build123d joints (default=False)
399
407
  parallel: Tessellate objects in parallel (default=False)
400
408
  show_parent: Render parent of faces, edges or vertices as wireframe
401
- helper_scale: Scale of rendered helpers (locations, axis, mates for MAssemblies) (default=1)
409
+ helper_scale: Scale of rendered helpers (locations, axis, mates for MAssemblies) (default=1)
402
410
 
403
411
  - Debug
404
412
  debug: Show debug statements to the VS Code browser console (default=False)
@@ -406,10 +414,6 @@ def show(
406
414
  """
407
415
  global LAST_CALL
408
416
 
409
- # if sys.gettrace() is not None and not _force_in_debug:
410
- # print("\nshow and show_object are ignored in debugging sessions\n")
411
- # return
412
-
413
417
  kwargs = {
414
418
  k: v
415
419
  for k, v in locals().items()
@@ -430,7 +434,17 @@ def show(
430
434
 
431
435
  timeit = preset("timeit", timeit)
432
436
 
433
- names = align_attrs(names, len(cad_objs), None, "names", explode=False)
437
+ if measure_tools is None:
438
+ measure_tools = get_default("measure_tools")
439
+ if measure_tools is None:
440
+ conf = workspace_config()
441
+ measure_tools = conf["measure_tools"]
442
+ kwargs["measure_tools"] = measure_tools
443
+
444
+ if measure_tools and kwargs.get("collapse") is None:
445
+ kwargs["collapse"] = Collapse.ROOT
446
+
447
+ names = align_attrs(names, len(cad_objs), None, "names", explode=measure_tools)
434
448
 
435
449
  # Handle colormaps
436
450
 
@@ -464,7 +478,7 @@ def show(
464
478
  progress = Progress([] if progress is None else [c for c in progress])
465
479
 
466
480
  with Timer(timeit, "", "overall"):
467
- data = _convert(
481
+ t, mapping = _convert(
468
482
  *cad_objs,
469
483
  names=names,
470
484
  colors=colors,
@@ -473,13 +487,16 @@ def show(
473
487
  **kwargs,
474
488
  )
475
489
 
476
- if not _force_in_debug:
477
- LAST_CALL = "show"
478
- else:
479
- LAST_CALL = "other"
490
+ if not _force_in_debug:
491
+ LAST_CALL = "show"
492
+ else:
493
+ LAST_CALL = "other"
480
494
 
481
495
  with Timer(timeit, "", "send"):
482
- return send_data(data, port=port, timeit=timeit)
496
+ send_data(t, port=port, timeit=timeit)
497
+
498
+ if measure_tools:
499
+ send_backend({"model": mapping}, port=port, timeit=timeit)
483
500
 
484
501
 
485
502
  def reset_show():
@@ -498,6 +515,7 @@ def show_object(
498
515
  progress="-+c",
499
516
  glass=None,
500
517
  tools=None,
518
+ measure_tools=None,
501
519
  tree_width=None,
502
520
  axes=None,
503
521
  axes0=None,
@@ -560,6 +578,7 @@ def show_object(
560
578
  Valid keywords to configure the viewer (**kwargs):
561
579
  - UI
562
580
  glass: Use glass mode where tree is an overlay over the cad object (default=False)
581
+ measure_tools: Show measure tools (default=False)
563
582
  tools: Show tools (default=True)
564
583
  tree_width: Width of the object tree (default=240)
565
584
 
@@ -674,10 +693,13 @@ def show_clear():
674
693
  send_data(data)
675
694
 
676
695
 
677
- def show_all(variables=None, exclude=None, **kwargs):
696
+ def show_all(variables=None, exclude=None, force=False, **kwargs):
678
697
  import inspect
679
698
 
680
- global FIRST_CALL, LAST_CALL
699
+ global LAST_CALL
700
+
701
+ if force:
702
+ LAST_CALL = "other"
681
703
 
682
704
  if LAST_CALL == "show":
683
705
  LAST_CALL = "other"
@@ -708,8 +730,8 @@ def show_all(variables=None, exclude=None, **kwargs):
708
730
  if hasattr(obj, "locations") and hasattr(obj, "local_locations"):
709
731
  obj = obj.locations
710
732
 
711
- if hasattr(obj, "to_location"):
712
- obj = obj.to_location()
733
+ if hasattr(obj, "local_coord_system"):
734
+ obj = obj.location
713
735
 
714
736
  if (
715
737
  (
@@ -729,6 +751,11 @@ def show_all(variables=None, exclude=None, **kwargs):
729
751
  and len(obj) > 0
730
752
  and hasattr(obj[0], "wrapped")
731
753
  )
754
+ or (
755
+ hasattr(obj, "wrapped")
756
+ and hasattr(obj, "position")
757
+ and hasattr(obj, "direction")
758
+ )
732
759
  ):
733
760
  objects.append(obj)
734
761
  names.append(name)
@@ -746,9 +773,6 @@ def show_all(variables=None, exclude=None, **kwargs):
746
773
  objects.append(pg)
747
774
  names.append(name)
748
775
 
749
- if FIRST_CALL:
750
- kwargs["reset_camera"] = Camera.RESET
751
-
752
776
  if len(objects) > 0:
753
777
  show(
754
778
  *objects,
@@ -757,6 +781,5 @@ def show_all(variables=None, exclude=None, **kwargs):
757
781
  _force_in_debug=True,
758
782
  **kwargs,
759
783
  )
760
- FIRST_CALL = False
761
784
  else:
762
785
  show_clear()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ocp-vscode
3
- Version: 1.2.2
3
+ Version: 2.0.2
4
4
  Summary: OCP CAD Viewer for VSCode
5
5
  Home-page: https://github.com/bernhard-42/vscode-ocp-cad-viewer
6
6
  Author: Bernhard Walter
@@ -18,4 +18,4 @@ Classifier: Programming Language :: Python :: 3.11
18
18
  Requires-Python: >=3.9
19
19
  License-File: LICENSE
20
20
 
21
- An extension to show OCP cad CAD objects (CadQuery, build123d) in VS Code via pythreejs
21
+ An extension to show OCP cad CAD objects (CadQuery, build123d) in VS Code via threejs
@@ -4,14 +4,19 @@ setup.cfg
4
4
  setup.py
5
5
  ocp_vscode/__init__.py
6
6
  ocp_vscode/animation.py
7
+ ocp_vscode/backend.py
7
8
  ocp_vscode/colors.py
8
9
  ocp_vscode/comms.py
9
10
  ocp_vscode/config.py
10
11
  ocp_vscode/finder.py
12
+ ocp_vscode/interactive.py
13
+ ocp_vscode/persistence.py
11
14
  ocp_vscode/show.py
12
15
  ocp_vscode.egg-info/PKG-INFO
13
16
  ocp_vscode.egg-info/SOURCES.txt
14
17
  ocp_vscode.egg-info/dependency_links.txt
15
18
  ocp_vscode.egg-info/not-zip-safe
16
19
  ocp_vscode.egg-info/requires.txt
17
- ocp_vscode.egg-info/top_level.txt
20
+ ocp_vscode.egg-info/top_level.txt
21
+ test/test_for_backend.py
22
+ test/testextension.py
@@ -1,4 +1,4 @@
1
- ocp-tessellate<1.2.2,>=1.1.2
1
+ ocp-tessellate<2.1.0,>=2.0.2
2
2
  requests
3
3
  ipykernel
4
4
  orjson
@@ -1,5 +1,5 @@
1
1
  [bumpversion]
2
- current_version = 1.2.2
2
+ current_version = 2.0.2
3
3
  commit = False
4
4
  tag = False
5
5
  parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?P<release>\D*)(?P<build>\d*)
@@ -2,13 +2,13 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup_args = {
4
4
  "name": "ocp_vscode",
5
- "version": "1.2.2",
5
+ "version": "2.0.2",
6
6
  "description": "OCP CAD Viewer for VSCode",
7
- "long_description": "An extension to show OCP cad CAD objects (CadQuery, build123d) in VS Code via pythreejs",
7
+ "long_description": "An extension to show OCP cad CAD objects (CadQuery, build123d) in VS Code via threejs",
8
8
  "include_package_data": True,
9
9
  "python_requires": ">=3.9",
10
10
  "install_requires": [
11
- "ocp-tessellate>=1.1.2,<1.2.2",
11
+ "ocp-tessellate>=2.0.2,<2.1.0",
12
12
  "requests",
13
13
  "ipykernel",
14
14
  "orjson",
@@ -0,0 +1,19 @@
1
+ from build123d import *
2
+ from ocp_vscode import *
3
+
4
+ # set_port(3939)
5
+ # set_defaults(reset_camera=Camera.KEEP, ortho=True)
6
+
7
+ with BuildPart() as p:
8
+ Box(10, 10, 10)
9
+ with Locations((5, 0, 0)):
10
+ Box(10, 10, 5)
11
+ c = Circle(10)
12
+ cc = extrude(c, 10)
13
+ for e in cc.faces()[0].edges():
14
+ print(e.geom_type())
15
+ v = Vertex(0, 0, 0)
16
+ # print(c.geom_type())
17
+ pass
18
+ # line = line.edges()[0]
19
+ # show(p, f1, f2)
@@ -0,0 +1,58 @@
1
+ from build123d import *
2
+ from ocp_vscode import *
3
+
4
+ set_port(3939)
5
+ set_defaults(reset_camera=Camera.KEEP, ortho=True)
6
+ densa = 7800 / 1e6 # carbon steel density g/mm^3
7
+ densb = 2700 / 1e6 # aluminum alloy
8
+ densc = 1020 / 1e6 # ABS
9
+ LB = 453.592 # g/lb
10
+ ms = Mode.SUBTRACT
11
+ LMH, LMV = LengthMode.HORIZONTAL, LengthMode.VERTICAL
12
+ # %%
13
+
14
+ with BuildPart() as p:
15
+ with BuildSketch(Plane.XZ) as s:
16
+ with BuildLine(Plane.XZ) as l:
17
+ m1 = Line((3.5 / 2, 0), (5.625 / 2, 0))
18
+ m2 = Line(m1 @ 1, m1 @ 1 + (0, 3))
19
+ m3 = Line(m2 @ 1, m2 @ 1 + (-(5.625 - 3.75) / 2, 0))
20
+ m4 = Line(m3 @ 1, m3 @ 1 + (0, -0.375))
21
+ m5 = Line(m4 @ 1, m4 @ 1 + (-0.75 / 2, 0))
22
+ m6 = Line(m5 @ 1, m5 @ 1 + (0, -3 + 1.75 + 0.375))
23
+ m7 = Line(m6 @ 1, m6 @ 1 + (0.5 / 2, 0))
24
+ m8 = Line(m7 @ 1, m1 @ 0)
25
+ make_face()
26
+ revolve(axis=Axis.Z, revolution_arc=90)
27
+
28
+ with BuildSketch(Plane.XZ.rotated((0, 0, -45))) as s2:
29
+ Rectangle(1, 1.5, align=(Align.CENTER, Align.MIN))
30
+ with Locations((0, 1)):
31
+ Rectangle(2, 0.25, align=(Align.CENTER, Align.MIN))
32
+ extrude(amount=-6, mode=ms)
33
+
34
+ with Locations(Plane.XY.offset(3)):
35
+ with PolarLocations(4.625 / 2, 1, 45):
36
+ CounterBoreHole(0.375 / 2, 0.625 / 2, 0.25)
37
+ mirror(about=Plane.XZ)
38
+ mirror(about=Plane.YZ)
39
+
40
+
41
+ r = RegularPolygon(100, 5)
42
+ pp = extrude(r, 1000)
43
+ pp -= Hole(2, 50)
44
+
45
+
46
+ classes = (BuildPart, BuildSketch, BuildLine) # for OCP-vscode
47
+ set_colormap(ColorMap.seeded(colormap="rgb", alpha=1, seed_value="vscod"))
48
+ show2(pp, name="item")
49
+
50
+ # b = Box(1, 2, 3) - Plane.YZ * Cylinder(0.5, 1)
51
+ # b = fillet(b.edges().filter_by(Axis.X), 0.3)
52
+ # b = chamfer(b.edges().filter_by(Axis.Y), 0.1)
53
+ # c = Pos(3, 0, 0) * Box(1, 1, 1)
54
+ # s = Pos(0, 2, 1) * Circle(0.5)
55
+
56
+ # show2(b, c, s)
57
+ print()
58
+ print(f"part mass = {p.part.scale(IN).volume*densb/LB}")
File without changes