Skip to content

Commit e52161e

Browse files
authored
Merge pull request #36 from FlachyJoe/dev
v 0.4.0
2 parents a350948 + d665fda commit e52161e

File tree

10 files changed

+107
-67
lines changed

10 files changed

+107
-67
lines changed

InitGui.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ def Initialize(self):
4848

4949
self.commandList = [
5050
"FCPD_Launch",
51+
"FCPD_Stop",
5152
"FCPD_AddInclude",
5253
"FCPD_AddPopulatedInclude",
5354
]
@@ -68,5 +69,8 @@ def ContextMenu(self, recipient):
6869
def GetClassName(self):
6970
return "Gui::PythonWorkbench"
7071

72+
def __del__(self):
73+
self.statusWidget.deleteLater()
74+
7175

7276
Gui.addWorkbench(FCPDWorkbench())

README.md

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,30 @@ Please see [FCPDWorkbench_Samples](https://github.com/FlachyJoe/FCPDWorkbench_Sa
4242

4343
## Requirements
4444

45-
The PySide2 (or PySide6) Python bindings for Qt5 (or Qt6) Network is required. Linux users can install python3-pyside2.qtnetwork (or python3-pyside6.qtnetwork) package.
46-
47-
External libraries are needed for Pure-Data :
45+
These external Pure-Data libraries are mandatory:
4846
* [list-abs](https://puredata.info/downloads/list-abs)
4947
* [iemlib](https://puredata.info/downloads/iemlib)
48+
* [iemguts](https://puredata.info/downloads/iemguts)
49+
50+
These external Pure-Data libraries are optional:
51+
* [pddp](https://puredata.info/downloads/pddp) used in documentation only
52+
53+
54+
See Pure-Data documentation to install them with the Deken package system\
55+
**OR** install a system package from your distribution repository\
56+
**OR** use an already populated Pure-Data clone as [Purr-Data](http://l2ork.music.vt.edu/main/make-your-own-l2ork/software/) or [PlugData](https://plugdata.org/).
57+
58+
These python libraries are optional:
59+
* [ikpy](https://github.com/Phylliade/ikpy) for inverse kinematic features
5060

51-
See Pure-Data documentation to install them with the Deken package system
52-
or install a system package from your distribution repository (e.g. `sudo apt install pd-list-abs pd-iemlib`)
53-
or use an already populated Pure-Data clone as [Purr-Data](http://l2ork.music.vt.edu/main/make-your-own-l2ork/software/) or [PlugData](https://plugdata.org/).
61+
### Debian like install
5462

55-
Inverse kinematic features request [ikpy](https://github.com/Phylliade/ikpy).
63+
For debian like linux distributions (Debian, Ubuntu, Mint, …) these command lines install all the needed:
64+
```bash
65+
sudo apt install puredata pd-list-abs pd-iemlib pd-iemguts
66+
sudo apt install pd-pddp
67+
```
5668

57-
=======
5869
## License
5970

6071
Copyright 2020-2025 @flachyjoe and other contributors
@@ -77,5 +88,5 @@ The Pure-Data part is coded in Pure-Data language. Thus it require some extra-li
7788

7889
As FUDI protocol can only deal with text, all the FreeCAD data are converted to be usable in Pure-Data. Some objects are still not string-representable. These ones are simply keeped in FreeCAD and Pure-Data can refer to them by reference indexes.
7990

80-
Due to the latency in client/server communication and FreeCAD stuff, *fcpd_** Pure-Data objects are **asynchronous**. So you can't know when outlets trigger. Nevertheless outlets are still triggered right to left.
91+
Due to the latency in client/server communication and FreeCAD stuff, `fc_` Pure-Data objects are **asynchronous**. So you can't know when outlets trigger. Nevertheless outlets are still triggered right to left.
8192
This breaks the usual PD codding and require some more work to let other objects wait for FC datas.

fcpd/__init__.py

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import os
2727
import sys
2828
import time
29+
import shutil
2930

3031
from PySide import QtGui, QtWidgets
3132
from PySide.QtCore import QProcess
@@ -60,23 +61,30 @@ def runPD():
6061
if not pdIsRunning():
6162
pdBin = userPref.GetString("pd_path")
6263

64+
if not shutil.which(pdBin):
65+
FreeCAD.Console.PrintError(
66+
f"Unable to find {pdBin}.\r\nPlease check the pure-data client binary path in the Edit menu/Preferences…/FCPD page."
67+
)
68+
return
69+
70+
pdlib = "pdlib"
71+
if not userPref.GetBool("pd_useExtend", True):
72+
pdlib = "pdlib.light"
73+
6374
pdArgs = [
6475
"-path",
65-
os.path.join(locator.PD_PATH, "pdlib"),
76+
os.path.join(locator.PD_PATH, pdlib),
6677
"-helppath",
6778
os.path.join(locator.PD_PATH, "pdhelp"),
6879
]
6980

70-
if userPref.GetBool("fc_allowRaw", False):
71-
clientTemplate = "client_raw.pdtemplate"
72-
pdArgs += [
73-
"-path",
74-
os.path.join(locator.PD_PATH, "pdautogen"),
75-
"-helppath",
76-
os.path.join(locator.PD_PATH, "pdautogenhelp"),
77-
]
78-
else:
79-
clientTemplate = "client.pdtemplate"
81+
clientTemplate = "client_raw.pdtemplate"
82+
pdArgs += [
83+
"-path",
84+
os.path.join(locator.PD_PATH, "pdautogen"),
85+
"-helppath",
86+
os.path.join(locator.PD_PATH, "pdautogenhelp"),
87+
]
8088

8189
with open(os.path.join(locator.PD_PATH, clientTemplate), "r") as f:
8290
clientContents = f.read()
@@ -92,6 +100,7 @@ def runPD():
92100
f.write(clientContents)
93101

94102
pdProcess.startDetached(pdBin, pdArgs + ["-open", clientFilePath])
103+
95104
if TRY2EMBED:
96105
time.sleep(1)
97106
embedPD()

fcpd/pdinclude.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def addCloseDetection(filePath):
6565
)
6666
fd.seek(0)
6767
fd.writelines(newContents)
68-
Log("close detection added\n")
68+
Log("FCPD", "close detection added\n")
6969

7070

7171
def hasCloseDetection(filePath):
@@ -90,7 +90,7 @@ def updateCloseDetection(filePath):
9090
newContents = contents[:lineNumber] + [onClose] + contents[lineNumber + 1 :]
9191
fd.seek(0)
9292
fd.writelines(newContents)
93-
Log("close detection updated\n")
93+
Log("FCPD", "close detection updated\n")
9494
else:
9595
addCloseDetection(filePath)
9696

@@ -137,7 +137,7 @@ def __init__(self, target_doc, caller, fileName):
137137

138138
def slotStartSaveDocument(self, doc, label):
139139
if doc == self.target_doc:
140-
Log("Ask PD to save\n")
140+
Log("FCPD", "Ask PD to save\n")
141141
self.caller.pdServer.send("0 pd-{self.fileName} menusave;")
142142
Gui.updateGui()
143143
# give PD 500ms to save
@@ -154,14 +154,14 @@ def fileChanged(self, filename):
154154
if self.skipChange > 0:
155155
self.skipChange -= 1
156156
elif os.path.exists(self.tmpFile):
157-
Log(f"{self.tmpFile} changed\n")
157+
Log("FCPD", f"{self.tmpFile} changed\n")
158158
self.object.PDFile = self.tmpFile
159159
App.ActiveDocument.recompute()
160160
else:
161-
Log(f"{self.tmpFile} deleted\n")
161+
Log("FCPD", f"{self.tmpFile} deleted\n")
162162

163163
def endEdit(self):
164-
Log(f"{self.tmpFile} closed\n")
164+
Log("FCPD", f"{self.tmpFile} closed\n")
165165
try:
166166
os.remove(self.tmpFile)
167167
os.remove(self.tmpFile + "_")

fcpd/pdmsgtranslator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ def valueFromStr(cls, words):
209209
index = int(words[0][1:])
210210
retValue = cls.objectsStore[index]
211211
retType = cls.OBJECT
212-
Log(f"{words[0]} refers to {str(retValue)}\n")
212+
Log("FCPD", f"{words[0]} refers to {str(retValue)}\n")
213213
usedWords = 1
214214
elif App.ActiveDocument is not None and App.ActiveDocument.getObject(
215215
words[0]

fcpd/pdrawtools.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ def pdRaw(pdServer, words):
328328
className = ".".join(modFunc[:-2])
329329
exec("import %s" % className)
330330
except ModuleNotFoundError as e:
331-
Wrn(f"Error : {e}\n")
331+
Wrn("FCPD", f"Error : {e}\n")
332332
return f"ERROR module not found {className}"
333333
obj = eval(objectName)
334334
func = getattr(obj, funcName)
@@ -360,25 +360,25 @@ def pdGiveMe(pdServer, words):
360360
filePath = os.path.join(modulePath, funcName)
361361

362362
if not os.path.isfile(filePath):
363-
Log(f"PDServer : add {filePath}\n")
363+
Log("FCPD", f"PDServer : add {filePath}\n")
364364
try:
365-
Log(f"PDServer : try to import {moduleName}\n")
365+
Log("FCPD", f"PDServer : try to import {moduleName}\n")
366366
exec(f"import {moduleName}")
367-
Log("PDServer : import ok\n")
367+
Log("FCPD", "PDServer : import ok\n")
368368
except ModuleNotFoundError:
369369
if args[:-2]:
370370
try:
371371
className = ".".join(args[:-2])
372-
Log(f"PDServer : try to import {className}\n")
372+
Log("FCPD", f"PDServer : try to import {className}\n")
373373
exec(f"import {className}")
374-
Log("PDServer : import ok\n")
374+
Log("FCPD", "PDServer : import ok\n")
375375
except ModuleNotFoundError as e:
376-
Wrn(f"Error : {e}\n")
376+
Wrn("FCPD", f"Error : {e}\n")
377377
return f"ERROR module not found {className}"
378378
try:
379379
obj = eval(moduleName)
380380
except Exception as e:
381-
Wrn(f"Error : {e}\n")
381+
Wrn("FCPD", f"Error : {e}\n")
382382
if hasattr(obj, funcName):
383383
func = getattr(obj, funcName)
384384
if not generate(func, words[2], modulePath, paramCount):

fcpd/pdserver.py

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
Msg = App.Console.PrintMessage
4747
Wrn = App.Console.PrintWarning
4848
Err = App.Console.PrintError
49+
Notif = App.Console.PrintNotification
4950

5051

5152
## Deal with PureData connection
@@ -122,7 +123,7 @@ def _pdMsgListProcessor(self, msgList):
122123
for msg in msgList:
123124
# remove trailing semicolon and newline
124125
msg = msg[:-2]
125-
Log(f"PDServer : <<<{msg}\r\n")
126+
Log("FCPD", f"PDServer : <<<{msg}\r\n")
126127

127128
# split words
128129
words = msg.split(" ")
@@ -134,16 +135,21 @@ def _pdMsgListProcessor(self, msgList):
134135
if self.outputSocket.waitForConnected(1000):
135136
self.isWaiting = False
136137
Log(
137-
f"PDServer : Callback initialized to {self.remoteAddress.toString()}:{words[1]}\n"
138+
"FCPD",
139+
f"PDServer : Callback initialized to {self.remoteAddress.toString()}:{words[1]}\n",
138140
)
139141
if self.writeBuffer:
140-
Wrn("PDServer : The data previously stored are now sent\n")
142+
Wrn(
143+
"FCPD",
144+
"PDServer : The data previously stored are now sent\n",
145+
)
141146
self.outputSocket.write(bytes(self.writeBuffer, "utf8"))
142-
Log(f"PDServer : >>> {self.writeBuffer}\r\n")
147+
Log("FCPD", f"PDServer : >>> {self.writeBuffer}\r\n")
143148
self.writeBuffer = ""
144149
else:
145150
Log(
146-
f"PDServer : ERROR during callback initialization\n{self.outputSocket.error()}\n"
151+
"FCPD",
152+
f"PDServer : ERROR during callback initialization\n{self.outputSocket.error()}\n",
147153
)
148154
elif words[0] == "close":
149155
self.terminate()
@@ -173,12 +179,13 @@ def send(self, *data):
173179
writeBuffer += ";\n"
174180
if self.isAvailable() and self.outputSocket.isOpen():
175181
self.outputSocket.write(bytes(writeBuffer, "utf8"))
176-
Log(f"PDServer : >>> {writeBuffer}\r\n")
182+
Log("FCPD", f"PDServer : >>> {writeBuffer}\r\n")
177183
else:
178184
self.writeBuffer = writeBuffer
179185
Wrn(
186+
"FCPD",
180187
"WARNING : Data are sent to PDServer but Pure-Data is not connected.\n"
181-
"The data will be kept until connection.\n"
188+
"The data will be kept until connection.\n",
182189
)
183190

184191
## launch the server
@@ -188,16 +195,19 @@ def run(self):
188195
if self.tcpServer.listen(QHostAddress(self.listenAddress), self.listenPort):
189196
self.isRunning = True
190197
self.isWaiting = True
191-
Log(f"PDServer : Listening on port {self.listenPort}\r\n")
198+
Log("FCPD", f"PDServer : Listening on port {self.listenPort}\r\n")
199+
Notif("FCPD", "The server is waiting for a PureData connection.")
192200
else:
193-
Err(f"PDServer : unable to listen port {self.listenPort}\r\n")
201+
Err("FCPD", f"PDServer : unable to listen port {self.listenPort}\r\n")
194202

195203
## Ask the server to terminate
196204
# @param self
197205
def terminate(self):
198-
self.outputSocket.write(b"0 close;")
199-
self.outputSocket.disconnectFromHost()
200-
self.inputSocket.disconnectFromHost()
206+
if self.outputSocket:
207+
self.outputSocket.write(b"0 close;")
208+
self.outputSocket.disconnectFromHost()
209+
if self.inputSocket:
210+
self.inputSocket.disconnectFromHost()
201211
self.isRunning = False
202212

203213
def newConnection(self):
@@ -207,8 +217,10 @@ def newConnection(self):
207217
self.tcpServer.close() # no new connection accepted
208218
self.remoteAddress = self.inputSocket.peerAddress()
209219
Log(
210-
f"PDServer : Connection from {self.remoteAddress.toString()}:{self.inputSocket.peerPort()}\r\n"
220+
"FCPD",
221+
f"PDServer : Connection from {self.remoteAddress.toString()}:{self.inputSocket.peerPort()}\r\n",
211222
)
223+
Notif("FCPD", "The server is now connected.")
212224

213225
def readyRead(self):
214226
data = self.inputSocket.readAll()
@@ -228,7 +240,8 @@ def readyRead(self):
228240

229241
def remoteClose(self):
230242
Log(
231-
f"PDServer : {self.inputSocket.peerAddress().toString()} close connection\r\n"
243+
"FCPD",
244+
f"PDServer : {self.inputSocket.peerAddress().toString()} close connection\r\n",
232245
)
233246
if self.isRunning:
234247
self.terminate()

fcpdwb_commands.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,17 @@ def GetResources(self):
5656
}
5757

5858
def Activated(self):
59-
if not fcpd.pdIsRunning():
59+
if not fcpd.pdServer.isRunning:
6060
FreeCADGui.runCommand("FCPD_Run")
61+
62+
if not fcpd.pdIsRunning():
6163
fcpd.runPD()
6264
else:
6365
Log(QT_TRANSLATE_NOOP("FCPD_Launch", "Pure-Data is already running.\n"))
6466
return
6567

6668
def IsActive(self):
67-
# return FCPD.pdProcess is None or FCPD.pdProcess.poll() is not None
68-
return True
69+
return not fcpd.pdIsRunning()
6970

7071

7172
class FCPD_CommandRun:
@@ -114,8 +115,7 @@ def Activated(self):
114115
return
115116

116117
def IsActive(self):
117-
# return FCPD.pdServer.isRunning
118-
return True
118+
return fcpd.pdServer.isRunning
119119

120120

121121
class FCPD_CommandAddInclude:
@@ -180,8 +180,8 @@ def IsActive(self):
180180
return True
181181

182182

183-
FreeCADGui.addCommand('FCPD_Run', FCPD_CommandRun())
184-
FreeCADGui.addCommand('FCPD_Stop', FCPD_CommandStop())
183+
FreeCADGui.addCommand("FCPD_Run", FCPD_CommandRun())
184+
FreeCADGui.addCommand("FCPD_Stop", FCPD_CommandStop())
185185
FreeCADGui.addCommand("FCPD_Launch", FCPD_CommandLaunch())
186186
FreeCADGui.addCommand("FCPD_AddInclude", FCPD_CommandAddInclude())
187187
FreeCADGui.addCommand("FCPD_AddPopulatedInclude", FCPD_CommandAddPopulatedInclude())

package.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
<package format="1" xmlns="https://wiki.freecad.org/Package_Metadata">
33
<name>FCPDWorkbench</name>
44
<description>A FreeCAD workbench to use Pure-Data as a graphical macro langage.</description>
5-
<version>0.3.1</version>
6-
<date>2025-04-14</date>
5+
<version>0.4.0</version>
6+
<date>2025-04-16</date>
77
<maintainer email="[email protected]">Florian Foinant-Willig</maintainer>
88
<license file="LICENSE">GPL-3.0-or-later</license>
99
<url type="repository" branch="main">https://github.com/FlachyJoe/FCPDWorkbench</url>
@@ -17,7 +17,7 @@
1717
<subdirectory>./</subdirectory>
1818
<icon>resources/icons/FCPDLogo.svg</icon>
1919
<tag>developers</tag>
20-
<version>0.3.1</version>
20+
<version>0.4.0</version>
2121
</workbench>
2222

2323
<depend optional="true" type="python">ikpy</depend>

0 commit comments

Comments
 (0)