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.
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/PKG-INFO +2 -2
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/README.md +19 -15
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/__init__.py +23 -0
- ocp_vscode-2.0.2/ocp_vscode/backend.py +410 -0
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/comms.py +54 -5
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/config.py +8 -2
- ocp_vscode-2.0.2/ocp_vscode/interactive.py +9 -0
- ocp_vscode-2.0.2/ocp_vscode/persistence.py +123 -0
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/show.py +67 -44
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/PKG-INFO +2 -2
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/SOURCES.txt +6 -1
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/requires.txt +1 -1
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/setup.cfg +1 -1
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/setup.py +3 -3
- ocp_vscode-2.0.2/test/test_for_backend.py +19 -0
- ocp_vscode-2.0.2/test/testextension.py +58 -0
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/LICENSE +0 -0
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/animation.py +0 -0
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/colors.py +0 -0
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode/finder.py +0 -0
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/dependency_links.txt +0 -0
- {ocp_vscode-1.2.2 → ocp_vscode-2.0.2}/ocp_vscode.egg-info/not-zip-safe +0 -0
- {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:
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
-
|
|
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__ = [
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
|
|
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
|
|
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, "
|
|
712
|
-
obj = obj.
|
|
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:
|
|
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
|
|
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
|
|
@@ -2,13 +2,13 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup_args = {
|
|
4
4
|
"name": "ocp_vscode",
|
|
5
|
-
"version": "
|
|
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
|
|
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>=
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|