multiconn_archicad 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- multiconn_archicad-0.2.0/LICENSE +21 -0
- multiconn_archicad-0.2.0/PKG-INFO +249 -0
- multiconn_archicad-0.2.0/README.md +234 -0
- multiconn_archicad-0.2.0/pyproject.toml +54 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/__init__.py +43 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/actions/__init__.py +12 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/actions/connection_manager.py +59 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/actions/project_handler.py +92 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/actions/refresh.py +43 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/basic_types.py +223 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/conn_header.py +124 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/core_commands.py +49 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/dialog_handlers/__init__.py +11 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/dialog_handlers/dialog_handler_base.py +22 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/dialog_handlers/win_dialog_handler.py +93 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/dialog_handlers/win_int_handler_factory.py +64 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/errors.py +16 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/multi_conn.py +165 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/py.typed +0 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/standard_connection.py +36 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/utilities/__init__.py +0 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/utilities/async_utils.py +42 -0
- multiconn_archicad-0.2.0/src/multiconn_archicad/utilities/platform_utils.py +30 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Szamosi Máté
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: multiconn_archicad
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: MultiConn ArchiCAD is a connection object for ArchiCAD’s JSON API and its Python wrapper, designed to manage multiple open instances of ArchiCAD simultaneously.
|
|
5
|
+
Author-email: SzamosiMate <szamimate@yahoo.com>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: aiohttp>=3.11.11
|
|
9
|
+
Requires-Dist: archicad>=28.3000
|
|
10
|
+
Requires-Dist: psutil>=6.1.1
|
|
11
|
+
Requires-Dist: pywinauto>=0.6.9
|
|
12
|
+
Provides-Extra: dialog-handlers
|
|
13
|
+
Requires-Dist: pywinauto>=0.6.9; extra == 'dialog-handlers'
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
<img src="https://github.com/user-attachments/assets/370ad589-117c-44cb-9a4a-80bfcb734445" width="400px" />
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
|
|
20
|
+
**MultiConn ArchiCAD** is a Python-based connection object for ArchiCAD's JSON API and its Python wrapper. It is designed to manage multiple open instances of Archicad simultaneously, making it easier to execute commands across multiple instances.
|
|
21
|
+
|
|
22
|
+
[](https://github.com/SzamosiMate/multiconn_archicad/releases/latest)
|
|
23
|
+

|
|
24
|
+

|
|
25
|
+

|
|
26
|
+

|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **Multi-connection Support**: Connect to one, multiple, or all open instances of Archicad.
|
|
31
|
+
- **Seamless Integration**: Utilizes ArchiCAD's official Python package.
|
|
32
|
+
- **Tapir Add-On Support**: Run commands from the Tapir Archicad Add-On.
|
|
33
|
+
- **Efficient I/O Operations**: Handles connection management using concurrent or asynchronous code.
|
|
34
|
+
- **Project Management**: Find and open ArchiCAD projects programmatically.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
You can install the latest version of the package from the following link using `pip`:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install https://github.com/SzamosiMate/multiconn_archicad/releases/latest/download/multiconn_archicad-0.1.2-py3-none-any.whl
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The package depends on the [Tapir Archicad Add-On](https://github.com/ENZYME-APD/tapir-archicad-automation?tab=readme-ov-file). It is recommended to install the latest version of Tapir to access all features. While some functionality may work without the add-on, all tests have been conducted with it installed.
|
|
45
|
+
|
|
46
|
+
## Usage
|
|
47
|
+
|
|
48
|
+
**Disclaimer:** The connection object is functional but in the early stages of development. It is not thoroughly tested, and its interfaces may change in future updates.
|
|
49
|
+
|
|
50
|
+
### Actions - managing the connection
|
|
51
|
+
Actions allow you to manage the state of the connection object. You can connect to or disconnect from Archicad instances, quit instances, or refresh ports. All actions can have multiple types of inputs. For each type of input you have to call the corresponding method of the action. To connect to all available ArchiCAD instances, you have to call the .all() method on .connect ( e.g. `conn.connect.all()`). The aim of this method is to provide better autocompletion.
|
|
52
|
+
|
|
53
|
+
#### Example: Connection Management
|
|
54
|
+
```python
|
|
55
|
+
from multiconn_archicad import MultiConn, Port
|
|
56
|
+
|
|
57
|
+
conn = MultiConn()
|
|
58
|
+
|
|
59
|
+
# connect all ArchiCAD instances that were running at instantiation / the last refresh
|
|
60
|
+
conn.connect.all()
|
|
61
|
+
|
|
62
|
+
# disconnect from the instance at port 19723
|
|
63
|
+
conn.disconnect.from_ports(Port(19723))
|
|
64
|
+
|
|
65
|
+
# refresh all closed ports - ports with no running archicad instance
|
|
66
|
+
conn.refresh.closed_ports()
|
|
67
|
+
|
|
68
|
+
# close, and remove from the dict of open port headers the archicad instance specified by ConnHeader
|
|
69
|
+
conn.quit.from_headers(conn.open_port_headers[Port(19735)])
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Project Management
|
|
73
|
+
|
|
74
|
+
The MultiConn object provides actions to find and open ArchiCAD projects programmatically.
|
|
75
|
+
|
|
76
|
+
#### Finding ArchiCAD Instances
|
|
77
|
+
|
|
78
|
+
You can use the `find_archicad` action to locate a specific ArchiCAD instance from a `ConnHeader`.
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from multiconn_archicad import MultiConn, ConnHeader
|
|
82
|
+
|
|
83
|
+
conn = MultiConn()
|
|
84
|
+
conn_header = ConnHeader(Port(19723))
|
|
85
|
+
|
|
86
|
+
# Find the port for a specific connection header
|
|
87
|
+
port = conn.find_archicad.from_header(conn_header)
|
|
88
|
+
if port:
|
|
89
|
+
print(f"Found ArchiCAD instance at port: {port}")
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
#### Opening Projects
|
|
93
|
+
|
|
94
|
+
The `open_project` action allows you to programmatically open ArchiCAD projects.
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from multiconn_archicad import MultiConn, ConnHeader, TeamworkCredentials
|
|
98
|
+
|
|
99
|
+
conn = MultiConn()
|
|
100
|
+
|
|
101
|
+
# Open a project using a connection header
|
|
102
|
+
conn_header = ConnHeader.from_dict(saved_header_data)
|
|
103
|
+
port = conn.open_project.from_header(conn_header)
|
|
104
|
+
|
|
105
|
+
# For teamwork projects, you can provide credentials
|
|
106
|
+
credentials = TeamworkCredentials("username", "password")
|
|
107
|
+
port = conn.open_project.with_teamwork_credentials(conn_header, credentials)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Dialog Handling
|
|
111
|
+
|
|
112
|
+
MultiConn can automatically handle most dialog windows that appear when opening ArchiCAD projects. This is particularly useful for batch operations and automation scripts.
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
from multiconn_archicad import MultiConn, WinDialogHandler, win_int_handler_factory
|
|
116
|
+
|
|
117
|
+
# Create a MultiConn instance with a dialog handler
|
|
118
|
+
conn = MultiConn(dialog_handler=WinDialogHandler(win_int_handler_factory))
|
|
119
|
+
|
|
120
|
+
# Dialog windows will be automatically handled when opening projects
|
|
121
|
+
conn.open_project.from_header(conn_header)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
The current implementation includes:
|
|
125
|
+
- `EmptyDialogHandler`: Does nothing (default)
|
|
126
|
+
- `WinDialogHandler`: Waits for ArchiCAD to start, and monitors appearing dialogs. If dialog appears, searches for appropriate handler in win_int_handler factory. Only works on windows.
|
|
127
|
+
- `win_int_handler_factory`: Provides dialog handleing logic on a dialog by dialog basis for the INT language version. It is an example you should customize for your specific project needs. Even if you end up not modifying it, you should definitely know what it does for what dialog.
|
|
128
|
+
|
|
129
|
+
### Serialization
|
|
130
|
+
|
|
131
|
+
The MultiConn package allows you to save and load connection configurations, making it easier to work with specific projects across multiple sessions.
|
|
132
|
+
|
|
133
|
+
#### Saving Connection Headers
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
from multiconn_archicad import MultiConn, Port
|
|
137
|
+
|
|
138
|
+
conn = MultiConn()
|
|
139
|
+
conn.connect.all()
|
|
140
|
+
|
|
141
|
+
# Get a connection header
|
|
142
|
+
conn_header = conn.open_port_headers[Port(19723)]
|
|
143
|
+
|
|
144
|
+
# Convert to dictionary for serialization
|
|
145
|
+
header_dict = conn_header.to_dict()
|
|
146
|
+
|
|
147
|
+
# Save to file using your preferred method
|
|
148
|
+
import json
|
|
149
|
+
with open('conn_header.json', 'w') as f:
|
|
150
|
+
json.dump(header_dict, f)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
#### Loading Connection Headers
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
from multiconn_archicad import ConnHeader, TeamworkCredentials
|
|
157
|
+
|
|
158
|
+
# Load from file
|
|
159
|
+
import json
|
|
160
|
+
with open('conn_header.json', 'r') as f:
|
|
161
|
+
header_dict = json.load(f)
|
|
162
|
+
|
|
163
|
+
# Create a header from the dictionary
|
|
164
|
+
conn_header = ConnHeader.from_dict(header_dict)
|
|
165
|
+
|
|
166
|
+
# For teamwork projects, you need to provide credentials
|
|
167
|
+
if isinstance(conn_header.archicad_id, TeamworkProjectID):
|
|
168
|
+
credentials = TeamworkCredentials("username", "password")
|
|
169
|
+
# Use the credentials when opening the project
|
|
170
|
+
port = conn.open_project.with_teamwork_credentials(conn_header, credentials)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Note: Passwords are not stored in serialized connection headers for security reasons. You must provide them when loading teamwork projects.
|
|
174
|
+
|
|
175
|
+
### Running Commands
|
|
176
|
+
|
|
177
|
+
#### Single Archicad Instance
|
|
178
|
+
|
|
179
|
+
To run commands on one chosen ArchiCAD instance the `MultiConn` object has a connection called `primary`. Calling a command directly from the MultiConn object will send it to the `primary` instance. The `primary` connection can be changed by assigning any valid `Port`, or `ConnHeader` object to `MultiConn.primary`.
|
|
180
|
+
|
|
181
|
+
#### Example: Running Commands on a Single Archicad Instance
|
|
182
|
+
```python
|
|
183
|
+
from multiconn_archicad import MultiConn, Port
|
|
184
|
+
|
|
185
|
+
# After instantiation the primary connection will be the instance with the lowest port number (probably 19723)
|
|
186
|
+
conn = MultiConn()
|
|
187
|
+
|
|
188
|
+
# Set the primary connection to the instance running on port 19725
|
|
189
|
+
conn.primary = Port(19725)
|
|
190
|
+
|
|
191
|
+
# Prints project info from the instance on port 19725
|
|
192
|
+
print(conn.core.post_tapir_command("GetProjectInfo"))
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
#### Multiple Archicad Instances
|
|
196
|
+
|
|
197
|
+
The MultiConn object stores references to `ConnHeaders` for all open ports (ports, with a running ArchiCAD instance). The references are stored in a dictionary at `.open_port_headers`. This dictionary maps each port to its corresponding connection. Each `ConnHeader` object has its own command objects for each used command namespace. The MultiConn objects has properties to access 3 subsets of open ports based on the status of the `ConnHeaders`:
|
|
198
|
+
|
|
199
|
+
- **`active`**: Successfully connected instances.
|
|
200
|
+
- **`failed`**: Instances where the connection attempt failed.
|
|
201
|
+
- **`pending`**: Instances with no connection attempt made or disconnected.
|
|
202
|
+
|
|
203
|
+
#### Example: Running Commands on Multiple Archicad Instances
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
from multiconn_archicad import MultiConn
|
|
207
|
+
|
|
208
|
+
conn = MultiConn()
|
|
209
|
+
conn.connect.all()
|
|
210
|
+
|
|
211
|
+
# Explicit loop to gather elements from all active connections
|
|
212
|
+
elements = {}
|
|
213
|
+
for port, conn_header in conn.active.items():
|
|
214
|
+
elements[port] = conn_header.standard.commands.GetAllElements()
|
|
215
|
+
|
|
216
|
+
# Using dictionary comprehension
|
|
217
|
+
elements = {
|
|
218
|
+
port: conn_header.standard.commands.GetAllElements()
|
|
219
|
+
for port, conn_header in conn.active.items()
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### Namespaces
|
|
224
|
+
|
|
225
|
+
The aim of the module is to incorporate all solutions that let users automate ArchiCAD from python. The different solutions are separated into namespaces, accessed from properties of the connection object. One of the planned features is letting users supply a list of namespaces they want to use when creating the connections. At the moment there are only two namespaces:
|
|
226
|
+
|
|
227
|
+
- **`standard`**: The official ArchiCAD python wrapper
|
|
228
|
+
- **`core`**: A simple JSON based module that lets the users post official and tapir commands based on Tapir's ["aclib"](https://github.com/ENZYME-APD/tapir-archicad-automation/tree/main/archicad-addon/Examples/aclib)
|
|
229
|
+
|
|
230
|
+
#### Example: Using two namespaces together
|
|
231
|
+
```python
|
|
232
|
+
def run(conn: MultiConn | ConnHeader) -> dict[str, Any]:
|
|
233
|
+
elements = conn.standard.commands.GetAllElements()
|
|
234
|
+
command_parameters = {
|
|
235
|
+
"elements": [element.to_dict() for element in elements],
|
|
236
|
+
"highlightedColors": [[50, 255, 100, 100] for _ in range(len(elements))],
|
|
237
|
+
"wireframe3D": True,
|
|
238
|
+
"nonHighlightedColor": [0, 0, 255, 128],
|
|
239
|
+
}
|
|
240
|
+
return conn.core.post_tapir_command('HighlightElements', command_parameters)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
## Contributing
|
|
244
|
+
|
|
245
|
+
Contributions are welcome! Feel free to submit issues, feature requests, or pull requests to help improve MultiConn ArchiCAD.
|
|
246
|
+
|
|
247
|
+
## License
|
|
248
|
+
|
|
249
|
+
This project is licensed under the MIT License. See the LICENSE file for details.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
<img src="https://github.com/user-attachments/assets/370ad589-117c-44cb-9a4a-80bfcb734445" width="400px" />
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
|
|
5
|
+
**MultiConn ArchiCAD** is a Python-based connection object for ArchiCAD's JSON API and its Python wrapper. It is designed to manage multiple open instances of Archicad simultaneously, making it easier to execute commands across multiple instances.
|
|
6
|
+
|
|
7
|
+
[](https://github.com/SzamosiMate/multiconn_archicad/releases/latest)
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Multi-connection Support**: Connect to one, multiple, or all open instances of Archicad.
|
|
16
|
+
- **Seamless Integration**: Utilizes ArchiCAD's official Python package.
|
|
17
|
+
- **Tapir Add-On Support**: Run commands from the Tapir Archicad Add-On.
|
|
18
|
+
- **Efficient I/O Operations**: Handles connection management using concurrent or asynchronous code.
|
|
19
|
+
- **Project Management**: Find and open ArchiCAD projects programmatically.
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
You can install the latest version of the package from the following link using `pip`:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install https://github.com/SzamosiMate/multiconn_archicad/releases/latest/download/multiconn_archicad-0.1.2-py3-none-any.whl
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The package depends on the [Tapir Archicad Add-On](https://github.com/ENZYME-APD/tapir-archicad-automation?tab=readme-ov-file). It is recommended to install the latest version of Tapir to access all features. While some functionality may work without the add-on, all tests have been conducted with it installed.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
**Disclaimer:** The connection object is functional but in the early stages of development. It is not thoroughly tested, and its interfaces may change in future updates.
|
|
34
|
+
|
|
35
|
+
### Actions - managing the connection
|
|
36
|
+
Actions allow you to manage the state of the connection object. You can connect to or disconnect from Archicad instances, quit instances, or refresh ports. All actions can have multiple types of inputs. For each type of input you have to call the corresponding method of the action. To connect to all available ArchiCAD instances, you have to call the .all() method on .connect ( e.g. `conn.connect.all()`). The aim of this method is to provide better autocompletion.
|
|
37
|
+
|
|
38
|
+
#### Example: Connection Management
|
|
39
|
+
```python
|
|
40
|
+
from multiconn_archicad import MultiConn, Port
|
|
41
|
+
|
|
42
|
+
conn = MultiConn()
|
|
43
|
+
|
|
44
|
+
# connect all ArchiCAD instances that were running at instantiation / the last refresh
|
|
45
|
+
conn.connect.all()
|
|
46
|
+
|
|
47
|
+
# disconnect from the instance at port 19723
|
|
48
|
+
conn.disconnect.from_ports(Port(19723))
|
|
49
|
+
|
|
50
|
+
# refresh all closed ports - ports with no running archicad instance
|
|
51
|
+
conn.refresh.closed_ports()
|
|
52
|
+
|
|
53
|
+
# close, and remove from the dict of open port headers the archicad instance specified by ConnHeader
|
|
54
|
+
conn.quit.from_headers(conn.open_port_headers[Port(19735)])
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Project Management
|
|
58
|
+
|
|
59
|
+
The MultiConn object provides actions to find and open ArchiCAD projects programmatically.
|
|
60
|
+
|
|
61
|
+
#### Finding ArchiCAD Instances
|
|
62
|
+
|
|
63
|
+
You can use the `find_archicad` action to locate a specific ArchiCAD instance from a `ConnHeader`.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from multiconn_archicad import MultiConn, ConnHeader
|
|
67
|
+
|
|
68
|
+
conn = MultiConn()
|
|
69
|
+
conn_header = ConnHeader(Port(19723))
|
|
70
|
+
|
|
71
|
+
# Find the port for a specific connection header
|
|
72
|
+
port = conn.find_archicad.from_header(conn_header)
|
|
73
|
+
if port:
|
|
74
|
+
print(f"Found ArchiCAD instance at port: {port}")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### Opening Projects
|
|
78
|
+
|
|
79
|
+
The `open_project` action allows you to programmatically open ArchiCAD projects.
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
from multiconn_archicad import MultiConn, ConnHeader, TeamworkCredentials
|
|
83
|
+
|
|
84
|
+
conn = MultiConn()
|
|
85
|
+
|
|
86
|
+
# Open a project using a connection header
|
|
87
|
+
conn_header = ConnHeader.from_dict(saved_header_data)
|
|
88
|
+
port = conn.open_project.from_header(conn_header)
|
|
89
|
+
|
|
90
|
+
# For teamwork projects, you can provide credentials
|
|
91
|
+
credentials = TeamworkCredentials("username", "password")
|
|
92
|
+
port = conn.open_project.with_teamwork_credentials(conn_header, credentials)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Dialog Handling
|
|
96
|
+
|
|
97
|
+
MultiConn can automatically handle most dialog windows that appear when opening ArchiCAD projects. This is particularly useful for batch operations and automation scripts.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from multiconn_archicad import MultiConn, WinDialogHandler, win_int_handler_factory
|
|
101
|
+
|
|
102
|
+
# Create a MultiConn instance with a dialog handler
|
|
103
|
+
conn = MultiConn(dialog_handler=WinDialogHandler(win_int_handler_factory))
|
|
104
|
+
|
|
105
|
+
# Dialog windows will be automatically handled when opening projects
|
|
106
|
+
conn.open_project.from_header(conn_header)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The current implementation includes:
|
|
110
|
+
- `EmptyDialogHandler`: Does nothing (default)
|
|
111
|
+
- `WinDialogHandler`: Waits for ArchiCAD to start, and monitors appearing dialogs. If dialog appears, searches for appropriate handler in win_int_handler factory. Only works on windows.
|
|
112
|
+
- `win_int_handler_factory`: Provides dialog handleing logic on a dialog by dialog basis for the INT language version. It is an example you should customize for your specific project needs. Even if you end up not modifying it, you should definitely know what it does for what dialog.
|
|
113
|
+
|
|
114
|
+
### Serialization
|
|
115
|
+
|
|
116
|
+
The MultiConn package allows you to save and load connection configurations, making it easier to work with specific projects across multiple sessions.
|
|
117
|
+
|
|
118
|
+
#### Saving Connection Headers
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from multiconn_archicad import MultiConn, Port
|
|
122
|
+
|
|
123
|
+
conn = MultiConn()
|
|
124
|
+
conn.connect.all()
|
|
125
|
+
|
|
126
|
+
# Get a connection header
|
|
127
|
+
conn_header = conn.open_port_headers[Port(19723)]
|
|
128
|
+
|
|
129
|
+
# Convert to dictionary for serialization
|
|
130
|
+
header_dict = conn_header.to_dict()
|
|
131
|
+
|
|
132
|
+
# Save to file using your preferred method
|
|
133
|
+
import json
|
|
134
|
+
with open('conn_header.json', 'w') as f:
|
|
135
|
+
json.dump(header_dict, f)
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### Loading Connection Headers
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from multiconn_archicad import ConnHeader, TeamworkCredentials
|
|
142
|
+
|
|
143
|
+
# Load from file
|
|
144
|
+
import json
|
|
145
|
+
with open('conn_header.json', 'r') as f:
|
|
146
|
+
header_dict = json.load(f)
|
|
147
|
+
|
|
148
|
+
# Create a header from the dictionary
|
|
149
|
+
conn_header = ConnHeader.from_dict(header_dict)
|
|
150
|
+
|
|
151
|
+
# For teamwork projects, you need to provide credentials
|
|
152
|
+
if isinstance(conn_header.archicad_id, TeamworkProjectID):
|
|
153
|
+
credentials = TeamworkCredentials("username", "password")
|
|
154
|
+
# Use the credentials when opening the project
|
|
155
|
+
port = conn.open_project.with_teamwork_credentials(conn_header, credentials)
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Note: Passwords are not stored in serialized connection headers for security reasons. You must provide them when loading teamwork projects.
|
|
159
|
+
|
|
160
|
+
### Running Commands
|
|
161
|
+
|
|
162
|
+
#### Single Archicad Instance
|
|
163
|
+
|
|
164
|
+
To run commands on one chosen ArchiCAD instance the `MultiConn` object has a connection called `primary`. Calling a command directly from the MultiConn object will send it to the `primary` instance. The `primary` connection can be changed by assigning any valid `Port`, or `ConnHeader` object to `MultiConn.primary`.
|
|
165
|
+
|
|
166
|
+
#### Example: Running Commands on a Single Archicad Instance
|
|
167
|
+
```python
|
|
168
|
+
from multiconn_archicad import MultiConn, Port
|
|
169
|
+
|
|
170
|
+
# After instantiation the primary connection will be the instance with the lowest port number (probably 19723)
|
|
171
|
+
conn = MultiConn()
|
|
172
|
+
|
|
173
|
+
# Set the primary connection to the instance running on port 19725
|
|
174
|
+
conn.primary = Port(19725)
|
|
175
|
+
|
|
176
|
+
# Prints project info from the instance on port 19725
|
|
177
|
+
print(conn.core.post_tapir_command("GetProjectInfo"))
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
#### Multiple Archicad Instances
|
|
181
|
+
|
|
182
|
+
The MultiConn object stores references to `ConnHeaders` for all open ports (ports, with a running ArchiCAD instance). The references are stored in a dictionary at `.open_port_headers`. This dictionary maps each port to its corresponding connection. Each `ConnHeader` object has its own command objects for each used command namespace. The MultiConn objects has properties to access 3 subsets of open ports based on the status of the `ConnHeaders`:
|
|
183
|
+
|
|
184
|
+
- **`active`**: Successfully connected instances.
|
|
185
|
+
- **`failed`**: Instances where the connection attempt failed.
|
|
186
|
+
- **`pending`**: Instances with no connection attempt made or disconnected.
|
|
187
|
+
|
|
188
|
+
#### Example: Running Commands on Multiple Archicad Instances
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from multiconn_archicad import MultiConn
|
|
192
|
+
|
|
193
|
+
conn = MultiConn()
|
|
194
|
+
conn.connect.all()
|
|
195
|
+
|
|
196
|
+
# Explicit loop to gather elements from all active connections
|
|
197
|
+
elements = {}
|
|
198
|
+
for port, conn_header in conn.active.items():
|
|
199
|
+
elements[port] = conn_header.standard.commands.GetAllElements()
|
|
200
|
+
|
|
201
|
+
# Using dictionary comprehension
|
|
202
|
+
elements = {
|
|
203
|
+
port: conn_header.standard.commands.GetAllElements()
|
|
204
|
+
for port, conn_header in conn.active.items()
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Namespaces
|
|
209
|
+
|
|
210
|
+
The aim of the module is to incorporate all solutions that let users automate ArchiCAD from python. The different solutions are separated into namespaces, accessed from properties of the connection object. One of the planned features is letting users supply a list of namespaces they want to use when creating the connections. At the moment there are only two namespaces:
|
|
211
|
+
|
|
212
|
+
- **`standard`**: The official ArchiCAD python wrapper
|
|
213
|
+
- **`core`**: A simple JSON based module that lets the users post official and tapir commands based on Tapir's ["aclib"](https://github.com/ENZYME-APD/tapir-archicad-automation/tree/main/archicad-addon/Examples/aclib)
|
|
214
|
+
|
|
215
|
+
#### Example: Using two namespaces together
|
|
216
|
+
```python
|
|
217
|
+
def run(conn: MultiConn | ConnHeader) -> dict[str, Any]:
|
|
218
|
+
elements = conn.standard.commands.GetAllElements()
|
|
219
|
+
command_parameters = {
|
|
220
|
+
"elements": [element.to_dict() for element in elements],
|
|
221
|
+
"highlightedColors": [[50, 255, 100, 100] for _ in range(len(elements))],
|
|
222
|
+
"wireframe3D": True,
|
|
223
|
+
"nonHighlightedColor": [0, 0, 255, 128],
|
|
224
|
+
}
|
|
225
|
+
return conn.core.post_tapir_command('HighlightElements', command_parameters)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
## Contributing
|
|
229
|
+
|
|
230
|
+
Contributions are welcome! Feel free to submit issues, feature requests, or pull requests to help improve MultiConn ArchiCAD.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
This project is licensed under the MIT License. See the LICENSE file for details.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "multiconn_archicad"
|
|
3
|
+
version = "0.2.0"
|
|
4
|
+
description = "MultiConn ArchiCAD is a connection object for ArchiCAD’s JSON API and its Python wrapper, designed to manage multiple open instances of ArchiCAD simultaneously."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "SzamosiMate", email = "szamimate@yahoo.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"aiohttp>=3.11.11",
|
|
12
|
+
"archicad>=28.3000",
|
|
13
|
+
"psutil>=6.1.1",
|
|
14
|
+
"pywinauto>=0.6.9",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
[project.optional-dependencies]
|
|
18
|
+
dialog-handlers = [
|
|
19
|
+
"pywinauto>=0.6.9",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["hatchling"]
|
|
24
|
+
build-backend = "hatchling.build"
|
|
25
|
+
[tool.hatch.build.targets.sdist]
|
|
26
|
+
include = [
|
|
27
|
+
"pkg/*.py",
|
|
28
|
+
"/src",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[dependency-groups]
|
|
32
|
+
dev = [
|
|
33
|
+
"mypy>=1.14.0",
|
|
34
|
+
"pytest>=8.3.4",
|
|
35
|
+
"ruff>=0.8.4",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[[tool.mypy.overrides]]
|
|
39
|
+
module = ["archicad.versioning",
|
|
40
|
+
"archicad.connection",
|
|
41
|
+
"archicad.releases",
|
|
42
|
+
"pywinauto",
|
|
43
|
+
"pywinauto.controls.uiawrapper",
|
|
44
|
+
"psutil"]
|
|
45
|
+
follow_untyped_imports = true
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 120
|
|
49
|
+
|
|
50
|
+
[tool.ruff.format]
|
|
51
|
+
indent-style = "space"
|
|
52
|
+
quote-style = "double"
|
|
53
|
+
line-ending = "lf"
|
|
54
|
+
docstring-code-format = true
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from .multi_conn import MultiConn
|
|
2
|
+
from .conn_header import ConnHeader
|
|
3
|
+
from .basic_types import (
|
|
4
|
+
ArchiCadID,
|
|
5
|
+
TeamworkProjectID,
|
|
6
|
+
SoloProjectID,
|
|
7
|
+
UntitledProjectID,
|
|
8
|
+
TeamworkCredentials,
|
|
9
|
+
ProductInfo,
|
|
10
|
+
ArchicadLocation,
|
|
11
|
+
Port,
|
|
12
|
+
APIResponseError,
|
|
13
|
+
FromAPIResponse,
|
|
14
|
+
)
|
|
15
|
+
from .standard_connection import StandardConnection
|
|
16
|
+
from .core_commands import CoreCommands
|
|
17
|
+
from .dialog_handlers import (
|
|
18
|
+
DialogHandlerBase,
|
|
19
|
+
WinDialogHandler,
|
|
20
|
+
win_int_handler_factory,
|
|
21
|
+
UnhandledDialogError,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
__all__: tuple[str, ...] = (
|
|
25
|
+
"MultiConn",
|
|
26
|
+
"ConnHeader",
|
|
27
|
+
"ArchiCadID",
|
|
28
|
+
"APIResponseError",
|
|
29
|
+
"FromAPIResponse",
|
|
30
|
+
"ProductInfo",
|
|
31
|
+
"Port",
|
|
32
|
+
"StandardConnection",
|
|
33
|
+
"CoreCommands",
|
|
34
|
+
"TeamworkCredentials",
|
|
35
|
+
"DialogHandlerBase",
|
|
36
|
+
"WinDialogHandler",
|
|
37
|
+
"win_int_handler_factory",
|
|
38
|
+
"UnhandledDialogError",
|
|
39
|
+
"TeamworkProjectID",
|
|
40
|
+
"SoloProjectID",
|
|
41
|
+
"UntitledProjectID",
|
|
42
|
+
"ArchicadLocation",
|
|
43
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from .connection_manager import Connect, QuitAndDisconnect, Disconnect
|
|
2
|
+
from .project_handler import FindArchicad, OpenProject
|
|
3
|
+
from .refresh import Refresh
|
|
4
|
+
|
|
5
|
+
__all__: tuple[str, ...] = (
|
|
6
|
+
"Connect",
|
|
7
|
+
"Disconnect",
|
|
8
|
+
"QuitAndDisconnect",
|
|
9
|
+
"Refresh",
|
|
10
|
+
"FindArchicad",
|
|
11
|
+
"OpenProject",
|
|
12
|
+
)
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from multiconn_archicad.conn_header import ConnHeader
|
|
7
|
+
from multiconn_archicad.multi_conn import MultiConn
|
|
8
|
+
from multiconn_archicad.basic_types import Port
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConnectionManager(ABC):
|
|
12
|
+
def __init__(self, multi_conn: MultiConn):
|
|
13
|
+
self.multi_conn: MultiConn = multi_conn
|
|
14
|
+
|
|
15
|
+
def from_ports(self, *args: Port) -> list[ConnHeader]:
|
|
16
|
+
return self.execute_action(
|
|
17
|
+
[
|
|
18
|
+
self.multi_conn.open_port_headers[port]
|
|
19
|
+
for port in args
|
|
20
|
+
if port in self.multi_conn.open_port_headers.keys()
|
|
21
|
+
]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
def from_headers(self, *args: ConnHeader) -> list[ConnHeader]:
|
|
25
|
+
return self.execute_action([*args])
|
|
26
|
+
|
|
27
|
+
def all(self) -> list[ConnHeader]:
|
|
28
|
+
return self.execute_action(list(self.multi_conn.open_port_headers.values()))
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def execute_action(self, conn_headers: list[ConnHeader]) -> list[ConnHeader]: ...
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class Connect(ConnectionManager):
|
|
35
|
+
def execute_action(self, conn_headers: list[ConnHeader]) -> list[ConnHeader]:
|
|
36
|
+
for conn_header in conn_headers:
|
|
37
|
+
print(f"connecting {conn_header.product_info}")
|
|
38
|
+
conn_header.connect()
|
|
39
|
+
return conn_headers
|
|
40
|
+
|
|
41
|
+
def failed(self) -> None:
|
|
42
|
+
self.execute_action(list(self.multi_conn.failed.values()))
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Disconnect(ConnectionManager):
|
|
46
|
+
def execute_action(self, conn_headers: list[ConnHeader]) -> list[ConnHeader]:
|
|
47
|
+
for conn_header in conn_headers:
|
|
48
|
+
conn_header.disconnect()
|
|
49
|
+
return conn_headers
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class QuitAndDisconnect(ConnectionManager):
|
|
53
|
+
def execute_action(self, conn_headers: list[ConnHeader]) -> list[ConnHeader]:
|
|
54
|
+
for conn_header in conn_headers:
|
|
55
|
+
if conn_header.port:
|
|
56
|
+
conn_header.core.post_tapir_command("QuitArchicad")
|
|
57
|
+
self.multi_conn.open_port_headers.pop(conn_header.port)
|
|
58
|
+
conn_header.unassign()
|
|
59
|
+
return conn_headers
|