├── .gitignore ├── LICENSE.md ├── README.md ├── change_parameters_of_selected_GDLbased.py ├── generate_building.py ├── get_parameters_of_selected_GDLbased.py ├── move_all_objects.py ├── open_all_hotlinks.py ├── recurring_publish.py └── utilities.py /.gitignore: -------------------------------------------------------------------------------- 1 | /__pycache__ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Tibor Lorántfy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ready-to-go Python scripts for Archicad 2 | 3 | [Download version 26.1](https://github.com/tlorantfy/archicad-python-scripts/archive/refs/tags/26.1.zip) 4 | 5 | - [recurring_publish.py](#recurring_publish.py) 6 | - [generate_building.py](#generate_building.py) 7 | - [move_all_objects.py](#move_all_objects.py) 8 | - [open_all_hotlinks.py](#open_all_hotlinks.py) 9 | - [get_parameters_of_selected_GDLbased.py](#get_parameters_of_selected_GDLbased.py) 10 | - [change_parameters_of_selected_GDLbased.py](#change_parameters_of_selected_GDLbased.py) 11 | 12 | ## recurring_publish.py 13 | 14 | ### Description 15 | The script can schedule recurring publishing. 16 | The related Archicad project must be opened before executing the script, because the script retrieves the projectdata during the start-up of the script. 17 | 18 | ### Requirements 19 | * **Requires Archicad 25 or later.** 20 | * [Additional JSON/Python Commands Add-On](https://github.com/tlorantfy/archicad-additional-json-commands) (version 25.2 or later) is required to be loaded into Archicad. 21 | * [Download the Add-On for Archicad 26 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.26.apx) 22 | * [Download the Add-On for Archicad 25 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.25.apx) 23 | * [Download the Add-On for Archicad 25 for macOS platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/25.2/archicad-additional-json-commands.bundle.zip) 24 | 25 | ### Features 26 | 27 | * Archicad will be shut down after each publishing and it will be restarted before each publishing, the project will be reloaded automatically. 28 | * User can choose from the Publisher Sets. Only the selected sets will be published. Multiple choice is available. 29 | * Recur time can be set in minutes. 30 | * Continuous progress report, the remaining time till the next publishing countdown appears. 31 | * Works for Teamwork (BIMcloud) projects also. A receive command is executed before each publishing. 32 | 33 | ### Usage 34 | 35 | 1. Running Archicad instance is required with an opened project and loaded [Additional JSON/Python Commands Add-On](https://github.com/tlorantfy/archicad-additional-json-commands/releases). 36 | 2. Run recurring_publish.py script from command line and with using python launcher. 37 | 3. Select Publisher Sets to publish. 38 | 4. Set time (in minutes) for recurring. 39 | 5. Click Start button. 40 | 41 | ### Demo video 42 | ![recurring_publish.py](https://j.gifs.com/lRY80V.gif) 43 | 44 | ## generate_building.py 45 | 46 | ### Description 47 | The script generates a skeleton building with slabs, columns and objects. 48 | 49 | ### Requirements 50 | * **Requires Archicad 25 or later.** 51 | * [Additional JSON/Python Commands Add-On](https://github.com/tlorantfy/archicad-additional-json-commands) (version 26.3 or later) is required to be loaded into Archicad. 52 | * [Download the Add-On for Archicad 26 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.26.apx) 53 | * [Download the Add-On for Archicad 25 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.25.apx) 54 | * [Download the Add-On for Archicad 25 for macOS platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/25.2/archicad-additional-json-commands.bundle.zip) 55 | 56 | ### Demo video 57 | ![generate_building.py](https://s1.ezgif.com/tmp/ezgif-1-fad163f9a4.gif) 58 | 59 | ## move_all_objects.py 60 | 61 | ### Description 62 | The script moves all the elements with Object type to a new position. 63 | 64 | ### Requirements 65 | * **Requires Archicad 25 or later.** 66 | * [Additional JSON/Python Commands Add-On](https://github.com/tlorantfy/archicad-additional-json-commands) (version 25.3 or later) is required to be loaded into Archicad. 67 | * [Download the Add-On for Archicad 26 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.26.apx) 68 | * [Download the Add-On for Archicad 25 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.25.apx) 69 | * [Download the Add-On for Archicad 25 for macOS platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/25.2/archicad-additional-json-commands.bundle.zip) 70 | 71 | ## open_all_hotlinks.py 72 | 73 | ### Description 74 | The script retrieves the list of hotlinks in the currently active project, opens the hotlinks one-by-one and performs an operation for each of them. 75 | 76 | ### Requirements 77 | * **Requires Archicad 25 or later.** 78 | * [Additional JSON/Python Commands Add-On](https://github.com/tlorantfy/archicad-additional-json-commands) (version 25.4 or later) is required to be loaded into Archicad. 79 | * [Download the Add-On for Archicad 26 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.26.apx) 80 | * [Download the Add-On for Archicad 25 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.25.apx) 81 | * [Download the Add-On for Archicad 25 for macOS platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/25.2/archicad-additional-json-commands.bundle.zip) 82 | 83 | ## get_parameters_of_selected_GDLbased.py 84 | 85 | ### Description 86 | The script lists the GDL parameters (name and value pairs) of the selected elements. 87 | 88 | ### Requirements 89 | * **Requires Archicad 26 or later.** 90 | * [Additional JSON/Python Commands Add-On](https://github.com/tlorantfy/archicad-additional-json-commands) (version 26.1 or later) is required to be loaded into Archicad. 91 | * [Download the Add-On for Archicad 26 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.26.apx) 92 | * [Download the Add-On for Archicad 25 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.25.apx) 93 | * [Download the Add-On for Archicad 25 for macOS platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/25.2/archicad-additional-json-commands.bundle.zip) 94 | 95 | ## change_parameters_of_selected_GDLbased.py 96 | 97 | ### Description 98 | The script changes the values of given GDL parameters of the selected elements. 99 | This examples changes the parameter with name 'gs_cont_pen' to pen with index 95. 100 | 101 | ### Requirements 102 | * **Requires Archicad 26 or later.** 103 | * [Additional JSON/Python Commands Add-On](https://github.com/tlorantfy/archicad-additional-json-commands) (version 26.1 or later) is required to be loaded into Archicad. 104 | * [Download the Add-On for Archicad 26 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.26.apx) 105 | * [Download the Add-On for Archicad 25 for Windows platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/26.3/archicad-additional-json-commands.25.apx) 106 | * [Download the Add-On for Archicad 25 for macOS platform](https://github.com/tlorantfy/archicad-additional-json-commands/releases/download/25.2/archicad-additional-json-commands.bundle.zip) -------------------------------------------------------------------------------- /change_parameters_of_selected_GDLbased.py: -------------------------------------------------------------------------------- 1 | from utilities import * 2 | 3 | acConnection = ConnectArchicad () 4 | 5 | elements = acConnection.commands.GetSelectedElements() 6 | 7 | elementsWithGDLParameters = [ { 'elementId' : { 'guid' : str (e.elementId.guid) }, 'gdlParameters' : { 'gs_cont_pen' : { 'value' : 95 } } } for e in elements ] 8 | 9 | response = ExecuteAdditionalJSONCommand ('ChangeGDLParametersOfElements', { 'elementsWithGDLParameters' : elementsWithGDLParameters }) 10 | ExitIfResponseIsError (response) -------------------------------------------------------------------------------- /generate_building.py: -------------------------------------------------------------------------------- 1 | from utilities import * 2 | 3 | acConnection = ConnectArchicad () 4 | 5 | origo = {'x': 0, 'y': 0, 'z': 0} 6 | storyHeight = 3.0 7 | slabWidth = 8.0 8 | slabHoleWidth = 4.0 9 | columnOffset = 0.3 10 | 11 | slabPolygonCoordinates = GetRectangleCoordinates (origo['x'], origo['y'], 12 | slabWidth, slabWidth) 13 | slabHolePolygonCoordinates = GetRectangleCoordinates (origo['x'], origo['y'], 14 | slabHoleWidth, slabHoleWidth) 15 | columnOrigo2DCoordinates = GetRectangleCoordinates (origo['x'], origo['y'], 16 | slabWidth-columnOffset, slabWidth-columnOffset) 17 | columnOrigo2DCoordinates += GetRectangleCoordinates (origo['x'], origo['y'], 18 | slabHoleWidth+columnOffset, slabHoleWidth+columnOffset) 19 | slabs = [{ 20 | 'level': origo['z'] + i * storyHeight, 21 | 'polygonCoordinates': slabPolygonCoordinates, 22 | 'holes': [{'polygonCoordinates': slabHolePolygonCoordinates}] 23 | } for i in range(5)] 24 | 25 | columnCoordinates = [{ 26 | 'x': coord2D['x'], 'y': coord2D['y'], 'z': i * storyHeight 27 | } for coord2D in columnOrigo2DCoordinates for i in range(4)] 28 | 29 | response = ExecuteAdditionalJSONCommand ('CreateSlabs', { 'slabs': slabs }) 30 | ExitIfResponseIsError (response) 31 | response = ExecuteAdditionalJSONCommand ('CreateColumns', { 'coordinates': columnCoordinates }) 32 | ExitIfResponseIsError (response) 33 | 34 | treeParameters = [{'name': 'Tree Model Detailed 26', 35 | 'coordinate': { 36 | 'x': origo['x'], 'y': origo['y'], 'z': origo['z'] 37 | }, 38 | 'dimensions': { 39 | 'x': slabHoleWidth, 'y': slabHoleWidth, 'z': storyHeight*5 40 | }}] 41 | 42 | response = ExecuteAdditionalJSONCommand ('CreateObjects', { 'objects': treeParameters }) 43 | ExitIfResponseIsError (response) -------------------------------------------------------------------------------- /get_parameters_of_selected_GDLbased.py: -------------------------------------------------------------------------------- 1 | from utilities import * 2 | from tkinter import messagebox 3 | 4 | acConnection = ConnectArchicad () 5 | 6 | elements = acConnection.commands.GetSelectedElements() 7 | 8 | response = ExecuteAdditionalJSONCommand ('GetGDLParametersOfElements', ConvertElementsResponseToInput (elements)) 9 | ExitIfResponseDoesNotContain (response, ['gdlParametersOfElements']) 10 | 11 | elementIdPropertyId = acConnection.utilities.GetBuiltInPropertyId('General_ElementID') 12 | elementIdPropertiesForElements = acConnection.commands.GetPropertyValuesOfElements(elements, [elementIdPropertyId]) 13 | 14 | for i in range(len(elements)): 15 | elementGuid = str (elements[i].elementId.guid) 16 | parameters = response['gdlParametersOfElements'][i] 17 | propertyValue = elementIdPropertiesForElements[i].propertyValues[0].propertyValue.value 18 | messagebox.showinfo ('GDL Parameters of ' + propertyValue + ' (Guid: ' + elementGuid + ')', '\n'.join([name + ' = ' + str (details['value']) for name, details in parameters.items ()])) -------------------------------------------------------------------------------- /move_all_objects.py: -------------------------------------------------------------------------------- 1 | from utilities import * 2 | 3 | acConnection = ConnectArchicad () 4 | objects = acConnection.commands.GetElementsByType ('Object') 5 | 6 | deltaX = 1.0 7 | deltaY = 1.0 8 | deltaZ = 0.0 9 | 10 | elementsWithMoveVectors = [{'elementId': {'guid': str (object.elementId.guid)}, 'moveVector': {'x': deltaX, 'y': deltaY, 'z': deltaZ}} for object in objects] 11 | 12 | response = ExecuteAdditionalJSONCommand ('MoveElements', { 'elementsWithMoveVectors': elementsWithMoveVectors }) 13 | ExitIfResponseIsError (response) -------------------------------------------------------------------------------- /open_all_hotlinks.py: -------------------------------------------------------------------------------- 1 | from utilities import * 2 | 3 | def GetHotlinks (): 4 | response = ExecuteAdditionalJSONCommand ('GetHotlinks') 5 | ExitIfResponseDoesNotContain (response, ['hotlinks']) 6 | return response['hotlinks'] 7 | 8 | def OpenHotlinkAndDo (hotlink, function): 9 | archicadLocation = GetArchicadLocation () 10 | StopArchicad () 11 | StartArchicadAndOpenProject (archicadLocation, hotlink['location']) 12 | 13 | function () 14 | 15 | if 'children' in hotlink: 16 | for childHotlink in hotlink['children']: 17 | OpenHotlinkAndDo (childHotlink, function) 18 | 19 | def DummyFunction (): 20 | print ("Implement here something to be executed for all hotlinks and the main project") 21 | 22 | ConnectArchicad () 23 | DummyFunction () 24 | 25 | for hotlink in GetHotlinks (): 26 | OpenHotlinkAndDo (hotlink, DummyFunction) 27 | 28 | StopArchicad () -------------------------------------------------------------------------------- /recurring_publish.py: -------------------------------------------------------------------------------- 1 | import os, re, platform, subprocess, tkinter as tk 2 | from utilities import * 3 | from archicad import ACConnection 4 | from tkinter import filedialog, messagebox 5 | from datetime import datetime, timedelta 6 | from threading import Timer 7 | 8 | ################################ CONFIGURATION ################################# 9 | dialogSize = '800x480' 10 | dialogTitle = 'Recurring Publish' 11 | textProject = 'Project:' 12 | textTeamworkUsername = 'Username:' 13 | textPublisherSets = 'Select Publisher Set(s) to Publish:' 14 | textOutputPath = 'Output Path:' 15 | textBrowseButton = 'Browse' 16 | textRecur1 = 'Recur every' 17 | textRecur2 = 'minute(s)' 18 | textPublishButton = 'Start Publishing' 19 | textExitButton = 'Stop and Exit' 20 | 21 | textProgressWaitingForStart = 'Waiting to start...' 22 | textProgressPublishing = 'Publishing...' 23 | textProgressSecsBackTillNextPublishing = ' second(s) till next publishing...' 24 | 25 | publishSubfolderPrefix = '' 26 | publishSubfolderDatePostfixFormat = '%Y-%m-%d_%H-%M-%S' 27 | ################################################################################ 28 | 29 | def GetProjectInfo (): 30 | response = ExecuteAdditionalJSONCommand ('GetProjectInfo') 31 | ExitIfResponseDoesNotContain (response, ['projectLocation', 'projectPath', 'isTeamwork']) 32 | return response 33 | 34 | archicadLocation = GetArchicadLocation () 35 | projectInfo = GetProjectInfo () 36 | taskScheduler = None 37 | publisherSetNames = [] 38 | 39 | class ProgressUpdater: 40 | def __init__ (self, secondsToCountBack): 41 | self.secondsToCountBack = secondsToCountBack 42 | self.elapsed = 0 43 | self.timer = Timer (1, self.Tick) 44 | self.timer.start () 45 | 46 | def Tick (self): 47 | self.elapsed += 1 48 | if self.Remaining () > 0: 49 | self.UpdateProgress () 50 | self.timer = Timer (1, self.Tick) 51 | self.timer.start () 52 | 53 | def Remaining (self): 54 | return self.secondsToCountBack - self.elapsed 55 | 56 | def UpdateProgress (self): 57 | progressLabel.config (text=f'{self.Remaining ()} {textProgressSecsBackTillNextPublishing}') 58 | 59 | 60 | class RecurringTaskScheduler: 61 | def __init__ (self, task): 62 | self.task = task 63 | 64 | def ScheduleNextExecution (self, secsToNextExecution = 1): 65 | self.timer = Timer (secsToNextExecution, self.Execute) 66 | self.timer.daemon = True 67 | self.timer.start () 68 | self.progressUpdater = ProgressUpdater (secsToNextExecution) 69 | 70 | def Stop (self): 71 | if self.timer: 72 | self.timer.cancel () 73 | self.StopArchicad () 74 | 75 | def Execute (self): 76 | StartArchicadAndOpenProject (archicadLocation, projectInfo['projectLocation']) 77 | self.task () 78 | StopArchicad () 79 | self.ScheduleNextExecution (timedelta (minutes=int (recurEntry.get ())).total_seconds ()) 80 | 81 | def ExecutePublishCommand (): 82 | progressLabel.config (text=textProgressPublishing) 83 | 84 | if projectInfo['isTeamwork']: 85 | response = ExecuteAdditionalJSONCommand ('TeamworkReceive') 86 | ExitIfResponseIsError (response) 87 | 88 | for publisherSetListIndex in publisherSetList.curselection (): 89 | publisherSetName = publisherSetNames[publisherSetListIndex] 90 | parameters = { 'publisherSetName': publisherSetName } 91 | if outputPathEntry.get (): 92 | parameters['outputPath'] = os.path.join ( 93 | outputPathEntry.get (), 94 | publisherSetName, 95 | f'{publishSubfolderPrefix}{datetime.now ().strftime(publishSubfolderDatePostfixFormat)}' 96 | ) 97 | 98 | response = ExecuteAdditionalJSONCommand ('Publish', parameters) 99 | ExitIfResponseIsError (response) 100 | 101 | def StartRecurringPublishing (): 102 | executeButton['state'] = tk.DISABLED 103 | exitButton['state'] = tk.NORMAL 104 | 105 | global taskScheduler 106 | taskScheduler = RecurringTaskScheduler (ExecutePublishCommand) 107 | taskScheduler.ScheduleNextExecution () 108 | 109 | def SetEntryValue (entry, text): 110 | entry.delete (0, tk.END) 111 | entry.insert (0, text) 112 | 113 | def BrowseOutputPath (): 114 | chosenPath = filedialog.askdirectory () 115 | if chosenPath: 116 | outputPathEntry['state'] = tk.NORMAL 117 | SetEntryValue (outputPathEntry, chosenPath) 118 | outputPathEntry['state'] = tk.DISABLED 119 | 120 | def GetUsernameFromProjectLocation (projectLocation): 121 | return re.compile (r'.*://(.*):.*@.*').match (projectLocation).group (1) 122 | 123 | def InitPublisherSetList (): 124 | global publisherSetNames 125 | publisherSetNames = ConnectArchicad ().commands.GetPublisherSetNames () 126 | publisherSetNames.sort () 127 | 128 | if publisherSetNames: 129 | for publisherSetName in publisherSetNames: 130 | publisherSetList.insert (tk.END, publisherSetName) 131 | publisherSetList.select_set (0) 132 | publisherSetList.event_generate ("<>") 133 | 134 | def InitControls (): 135 | InitPublisherSetList () 136 | SetEntryValue (projectEntry, projectInfo['projectPath']) 137 | if projectInfo['isTeamwork']: 138 | SetEntryValue (projectEntry, f'{projectEntry.get ()} (Teamwork project)') 139 | SetEntryValue (teamworkUsernameEntry, GetUsernameFromProjectLocation (projectInfo['projectLocation'])) 140 | SetEntryValue (recurEntry, '1') 141 | projectEntry['state'] = tk.DISABLED 142 | outputPathEntry['state'] = tk.DISABLED 143 | exitButton['state'] = tk.DISABLED 144 | teamworkUsernameEntry['state'] = tk.DISABLED 145 | 146 | 147 | app = tk.Tk () 148 | app.title (dialogTitle) 149 | 150 | def IntegerValidator (value): 151 | return str.isdigit (value) 152 | 153 | integerValidator = (app.register (IntegerValidator)) 154 | 155 | projectLabel = tk.Label (app, text=textProject) 156 | projectEntry = tk.Entry (app) 157 | teamworkUsernameLabel = tk.Label (app, text=textTeamworkUsername) 158 | teamworkUsernameEntry = tk.Entry (app) 159 | publisherSetLabel = tk.Label (app, text=textPublisherSets) 160 | publisherSetList = tk.Listbox (app, selectmode=tk.MULTIPLE) 161 | outputPathLabel = tk.Label (app, text=textOutputPath) 162 | outputPathEntry = tk.Entry (app) 163 | recurFrame = tk.Frame (app) 164 | recurLabel1 = tk.Label (recurFrame, text=textRecur1) 165 | recurEntry = tk.Entry (recurFrame, validate='all', validatecommand=(integerValidator, '%P')) 166 | recurLabel2 = tk.Label (recurFrame, text=textRecur2) 167 | progressLabel = tk.Label (app, text=textProgressWaitingForStart) 168 | outputPathBrowseButton = tk.Button (app, text=textBrowseButton, command=BrowseOutputPath) 169 | executeButton = tk.Button (app, text=textPublishButton, command=StartRecurringPublishing) 170 | exitButton = tk.Button (app, text=textExitButton, command=exit) 171 | 172 | projectLabel.grid (column=0, row=0, padx=10, sticky=tk.W) 173 | projectEntry.grid (column=1, row=0, columnspan=2, padx=10, sticky=tk.NSEW) 174 | teamworkUsernameLabel.grid (column=0, row=1, padx=10, sticky=tk.W) 175 | teamworkUsernameEntry.grid (column=1, row=1, columnspan=2, padx=10, sticky=tk.NSEW) 176 | publisherSetLabel.grid (column=0, row=2, columnspan=3, padx=10, sticky=tk.W) 177 | publisherSetList.grid (column=0, row=3, columnspan=3, padx=10, sticky=tk.NSEW) 178 | outputPathLabel.grid (column=0, row=4, columnspan=3, padx=10, sticky=tk.W) 179 | outputPathEntry.grid (column=0, row=5, padx=10, columnspan=2, sticky=tk.NSEW) 180 | outputPathBrowseButton.grid (column=2, row=5, padx=10, sticky=tk.NSEW) 181 | recurFrame.grid (column=0, row=6, columnspan=3, pady=10, padx=10, sticky=tk.NSEW) 182 | progressLabel.grid (column=0, row=7, columnspan=3, pady=10, padx=10, sticky=tk.W) 183 | executeButton.grid (column=0, row=8, columnspan=3, pady=5, padx=10, sticky=tk.NSEW) 184 | exitButton.grid (column=0, row=9, columnspan=3, pady=5, padx=10, sticky=tk.NSEW) 185 | 186 | recurLabel1.grid (column=0, row=0, padx=10, sticky=tk.W) 187 | recurEntry.grid (column=1, row=0, padx=10, sticky=tk.NSEW) 188 | recurLabel2.grid (column=2, row=0, padx=10, sticky=tk.W) 189 | 190 | app.columnconfigure (0, weight=1) 191 | app.columnconfigure (1, weight=8) 192 | app.rowconfigure (8, weight=1) 193 | app.geometry (dialogSize) 194 | 195 | InitControls () 196 | 197 | app.mainloop () -------------------------------------------------------------------------------- /utilities.py: -------------------------------------------------------------------------------- 1 | import platform, subprocess 2 | from archicad import ACConnection 3 | from tkinter import messagebox 4 | 5 | ################################ CONFIGURATION ################################# 6 | errorMessageTitleArchicadNotFound = 'Could not find Archicad!' 7 | errorMessageDetailsArchicadNotFound = 'A running Archicad instance is required to initialize the schedule.' 8 | 9 | errorMessageTitleAdditionalCommandsNotFound = 'Could not find the required commands!' 10 | errorMessageDetailsAdditionalCommandsNotFound = 'The below commands are not available:\n{}\n\nThe latest version of AdditionalJSONCommands Add-On is required.\nDownload it from github:\nhttps://github.com/tlorantfy/archicad-additional-json-commands/releases' 11 | 12 | errorMessageTitleCommandExecutionFailed = 'Failed to execute Archicad command!' 13 | ################################################################################ 14 | 15 | def ReconnectToArchicad (): 16 | return ACConnection.connect () 17 | 18 | def ConnectArchicad (): 19 | conn = ReconnectToArchicad () 20 | if not conn: 21 | messagebox.showerror (errorMessageTitleArchicadNotFound, errorMessageDetailsArchicadNotFound) 22 | exit () 23 | return conn 24 | 25 | def CheckCommandsAvailability (acConnection, additionalJSONCommands): 26 | notAvailableCommands = [commandId.commandName + ' (Namespace: ' + commandId.commandNamespace + ')' for commandId in additionalJSONCommands if not acConnection.commands.IsAddOnCommandAvailable (commandId)] 27 | if notAvailableCommands: 28 | messagebox.showerror (errorMessageTitleAdditionalCommandsNotFound, errorMessageDetailsAdditionalCommandsNotFound.format ('\n'.join (notAvailableCommands))) 29 | exit () 30 | 31 | def ExitIfResponseIsError (response): 32 | ExitIfResponseDoesNotContain (response) 33 | 34 | def ExitIfResponseDoesNotContain (response, requiredFields = None): 35 | missingFields = [] 36 | if requiredFields: 37 | for f in requiredFields: 38 | if f not in response: 39 | missingFields.append (f) 40 | if (len(response) > 0 and 'error' in response) or (len(missingFields) > 0): 41 | messagebox.showerror (errorMessageTitleCommandExecutionFailed, response) 42 | exit () 43 | 44 | def ConvertElementsResponseToInput (elements): 45 | return { 'elements' : [ { 'elementId' : { 'guid' : str (e.elementId.guid) } } for e in elements ] } 46 | 47 | def IsUsingMacOS (): 48 | return platform.system () == 'Darwin' 49 | 50 | def IsUsingWindows (): 51 | return platform.system () == 'Windows' 52 | 53 | def EscapeSpacesInPath (path): 54 | if IsUsingWindows (): 55 | return f'"{path}"' 56 | else: 57 | return path.replace (' ', '\\ ') 58 | 59 | def StartArchicadAndOpenProject (archicadLocation, projectLocation): 60 | acConnection = ReconnectToArchicad () 61 | if not acConnection: 62 | subprocess.Popen (f"{EscapeSpacesInPath (archicadLocation)} {EscapeSpacesInPath (projectLocation)}", start_new_session=True, shell=IsUsingMacOS ()) 63 | while not acConnection: 64 | acConnection = ReconnectToArchicad () 65 | 66 | def ExecuteAdditionalJSONCommand (commandName, inputParameters = None): 67 | acConnection = ConnectArchicad () 68 | command = acConnection.types.AddOnCommandId ('AdditionalJSONCommands', commandName) 69 | CheckCommandsAvailability (acConnection, [command]) 70 | return acConnection.commands.ExecuteAddOnCommand (command, inputParameters) 71 | 72 | def StopArchicad (): 73 | return ExecuteAdditionalJSONCommand ('Quit') 74 | 75 | def GetArchicadLocation (): 76 | response = ExecuteAdditionalJSONCommand ('GetArchicadLocation') 77 | ExitIfResponseDoesNotContain (response, ['archicadLocation']) 78 | if IsUsingMacOS (): 79 | return f"{response['archicadLocation']}/Contents/MacOS/ARCHICAD" 80 | return response['archicadLocation'] 81 | 82 | def GetRectangleCoordinates (centerX, centerY, width, height): 83 | return [ 84 | {'x': centerX + width/2, 'y': centerY - height/2}, 85 | {'x': centerX + width/2, 'y': centerY + height/2}, 86 | {'x': centerX - width/2, 'y': centerY + height/2}, 87 | {'x': centerX - width/2, 'y': centerY - height/2} 88 | ] --------------------------------------------------------------------------------