@deepblend/dsh-blender-provider-local 0.2.10 → 0.2.12
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.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deepblend/dsh-blender-provider-local",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.12",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Local Blender Provider: the BlenderRuntime Service over ctx.subprocess (batch execution).",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"./package.json": "./package.json"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@deepblend/dsh-blender-contracts": "0.2.
|
|
14
|
+
"@deepblend/dsh-blender-contracts": "0.2.12"
|
|
15
15
|
},
|
|
16
16
|
"license": "MIT",
|
|
17
17
|
"repository": {
|
|
Binary file
|
|
Binary file
|
|
@@ -90,6 +90,26 @@ bl_info = {
|
|
|
90
90
|
# add-on uses when a user enables it with no configuration.
|
|
91
91
|
DEFAULT_SOCKET = os.path.join(os.path.expanduser("~"), ".deepblend-bridge.sock")
|
|
92
92
|
|
|
93
|
+
# THE PER-WORKSPACE CONVENTION, and the reason it exists: the global default above is ONE path for the
|
|
94
|
+
# whole machine, so two workspaces with a Blender each collide there — the second refuses now, which is
|
|
95
|
+
# honest but is still the user's problem to solve by hand. When a bridge knows its workspace, it can
|
|
96
|
+
# simply listen somewhere that belongs to that workspace, and the collision never happens.
|
|
97
|
+
#
|
|
98
|
+
# The path is inside `.deepblend/`, which is the store and is gitignored — a socket is a runtime object,
|
|
99
|
+
# not a source file, and it must never be committable.
|
|
100
|
+
WORKSPACE_SOCKET_RELATIVE = os.path.join(".deepblend", "bridge.sock")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def default_socket_for(workspace):
|
|
104
|
+
"""Where a bridge should listen, given what it knows.
|
|
105
|
+
|
|
106
|
+
A workspace it knows gives a path that belongs to that workspace; a workspace it does not know
|
|
107
|
+
gives the machine-wide default, which is what every deployment had before this convention.
|
|
108
|
+
"""
|
|
109
|
+
if isinstance(workspace, str) and workspace != "":
|
|
110
|
+
return os.path.join(workspace, WORKSPACE_SOCKET_RELATIVE)
|
|
111
|
+
return DEFAULT_SOCKET
|
|
112
|
+
|
|
93
113
|
|
|
94
114
|
class _Bridge:
|
|
95
115
|
"""The serving state: one socket, one thread, and the last thing that happened."""
|
|
@@ -134,12 +154,40 @@ class _Bridge:
|
|
|
134
154
|
# Nothing answers, so it is the file a process that died left behind. Refusing here would
|
|
135
155
|
# leave the user with no way forward except deleting a file they cannot see.
|
|
136
156
|
os.unlink(self.socket_path)
|
|
157
|
+
# A workspace-derived path lives inside `.deepblend/`, which may not exist yet on a machine
|
|
158
|
+
# where the product has never run — and `bind` does not create directories.
|
|
159
|
+
parent = os.path.dirname(self.socket_path)
|
|
160
|
+
if parent and not os.path.isdir(parent):
|
|
161
|
+
os.makedirs(parent, exist_ok=True)
|
|
137
162
|
self.server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
138
163
|
self.server.bind(self.socket_path)
|
|
139
164
|
self.server.listen(4)
|
|
140
165
|
self.thread = threading.Thread(target=self._serve, name="deepblend-bridge", daemon=True)
|
|
141
166
|
self.thread.start()
|
|
142
167
|
|
|
168
|
+
@staticmethod
|
|
169
|
+
def derive_workspace(open_file):
|
|
170
|
+
"""The workspace a Blender belongs to, read from the file it has open — or nothing at all.
|
|
171
|
+
|
|
172
|
+
WHY THIS IS A DERIVATION AND NOT A GUESS. A checkpoint the product wrote lives at
|
|
173
|
+
`<workspace>/.deepblend/projects/<id>/revisions/<rev>/scene.blend`, so a Blender that has one
|
|
174
|
+
of those open IS working in that workspace — the path says so, in the product's own layout.
|
|
175
|
+
Anything else (an empty path, a file the user made themselves, a project laid out differently)
|
|
176
|
+
returns nothing, and nothing is what the product treats as "serves anything".
|
|
177
|
+
|
|
178
|
+
THAT ASYMMETRY IS THE POINT: a bridge that states a workspace it cannot be sure of would be
|
|
179
|
+
refused by the product for a mismatch the user did not cause, and the fix would look like a
|
|
180
|
+
configuration problem when it is a wrong guess. Silence is the honest answer when the path does
|
|
181
|
+
not say.
|
|
182
|
+
"""
|
|
183
|
+
if not isinstance(open_file, str) or open_file == "":
|
|
184
|
+
return ""
|
|
185
|
+
marker = os.sep + ".deepblend" + os.sep
|
|
186
|
+
head, separator, _ = open_file.partition(marker)
|
|
187
|
+
if separator == "" or head == "":
|
|
188
|
+
return ""
|
|
189
|
+
return head
|
|
190
|
+
|
|
143
191
|
def _someone_is_listening(self):
|
|
144
192
|
"""Whether a live bridge is behind this path, rather than a file left by a dead one."""
|
|
145
193
|
probe = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
@@ -291,8 +339,12 @@ class DEEPBLEND_PT_bridge(bpy.types.Panel):
|
|
|
291
339
|
|
|
292
340
|
def register():
|
|
293
341
|
global _BRIDGE
|
|
294
|
-
|
|
295
|
-
|
|
342
|
+
workspace = os.environ.get("DEEPBLEND_BRIDGE_WORKSPACE", "")
|
|
343
|
+
if workspace == "":
|
|
344
|
+
# In a GUI the user has a file open, and if the product wrote it, it names the workspace.
|
|
345
|
+
workspace = _Bridge.derive_workspace(getattr(bpy.data, "filepath", ""))
|
|
346
|
+
socket_path = os.environ.get("DEEPBLEND_BRIDGE_SOCKET") or default_socket_for(workspace)
|
|
347
|
+
_BRIDGE = _Bridge(socket_path, {"workspace": workspace})
|
|
296
348
|
try:
|
|
297
349
|
_BRIDGE.start()
|
|
298
350
|
except Exception as exc:
|
|
@@ -330,7 +382,13 @@ def main():
|
|
|
330
382
|
index += 2
|
|
331
383
|
continue
|
|
332
384
|
index += 1
|
|
333
|
-
|
|
385
|
+
if workspace == "":
|
|
386
|
+
# WHAT THE OPEN FILE SAYS, when it says anything. An operator's explicit setting always wins;
|
|
387
|
+
# this only fills a silence, and only from a path that follows the product's own layout.
|
|
388
|
+
workspace = _Bridge.derive_workspace(getattr(bpy.data, "filepath", ""))
|
|
389
|
+
# THE SOCKET FOLLOWS THE WORKSPACE, so two workspaces with a Blender each do not collide at all.
|
|
390
|
+
# An explicit DEEPBLEND_BRIDGE_SOCKET still wins, exactly as an explicit workspace does.
|
|
391
|
+
socket_path = options.get("socket") or os.environ.get("DEEPBLEND_BRIDGE_SOCKET") or default_socket_for(workspace)
|
|
334
392
|
|
|
335
393
|
if bootstrap is None:
|
|
336
394
|
print(json.dumps({"kind": "error", "message": BOOTSTRAP_PROBLEM}), flush=True)
|