├── .gitignore ├── LICENSE ├── README.md ├── bimbots.FCMacro ├── bimbots.py ├── bimbots.ui ├── doc ├── documentation.md ├── images │ ├── bimbots-ui-01.jpg │ ├── bimbots-ui-02.jpg │ ├── bimbots-ui-03.jpg │ ├── bimbots-ui-04.jpg │ ├── bimbots-ui-05.jpg │ ├── bimbots-ui-06.jpg │ ├── bimbots-ui-07.jpg │ ├── bimbots-ui-08.jpg │ ├── bimbots-ui-09.jpg │ ├── bimbots-ui-10.jpg │ └── bimbots-ui-11.jpg ├── implementation-notes.md └── ui-documentation.md ├── icons ├── BIM-Bots-header.png ├── BIM-Bots-validationchecker.png ├── BIM-Bots-viewer.png ├── BIMBots.svg └── Tango-Computer.svg ├── testfiles ├── test payload response.json ├── test payload.ifc └── test response.bcf.zip └── translations ├── README.md └── bimbots.ts /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | admin 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 2.1, February 1999 3 | 4 | Copyright (C) 1991, 1999 Free Software Foundation, Inc. 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | [This is the first released version of the Lesser GPL. It also counts 10 | as the successor of the GNU Library Public License, version 2, hence 11 | the version number 2.1.] 12 | 13 | Preamble 14 | 15 | The licenses for most software are designed to take away your 16 | freedom to share and change it. By contrast, the GNU General Public 17 | Licenses are intended to guarantee your freedom to share and change 18 | free software--to make sure the software is free for all its users. 19 | 20 | This license, the Lesser General Public License, applies to some 21 | specially designated software packages--typically libraries--of the 22 | Free Software Foundation and other authors who decide to use it. You 23 | can use it too, but we suggest you first think carefully about whether 24 | this license or the ordinary General Public License is the better 25 | strategy to use in any particular case, based on the explanations below. 26 | 27 | When we speak of free software, we are referring to freedom of use, 28 | not price. Our General Public Licenses are designed to make sure that 29 | you have the freedom to distribute copies of free software (and charge 30 | for this service if you wish); that you receive source code or can get 31 | it if you want it; that you can change the software and use pieces of 32 | it in new free programs; and that you are informed that you can do 33 | these things. 34 | 35 | To protect your rights, we need to make restrictions that forbid 36 | distributors to deny you these rights or to ask you to surrender these 37 | rights. These restrictions translate to certain responsibilities for 38 | you if you distribute copies of the library or if you modify it. 39 | 40 | For example, if you distribute copies of the library, whether gratis 41 | or for a fee, you must give the recipients all the rights that we gave 42 | you. You must make sure that they, too, receive or can get the source 43 | code. If you link other code with the library, you must provide 44 | complete object files to the recipients, so that they can relink them 45 | with the library after making changes to the library and recompiling 46 | it. And you must show them these terms so they know their rights. 47 | 48 | We protect your rights with a two-step method: (1) we copyright the 49 | library, and (2) we offer you this license, which gives you legal 50 | permission to copy, distribute and/or modify the library. 51 | 52 | To protect each distributor, we want to make it very clear that 53 | there is no warranty for the free library. Also, if the library is 54 | modified by someone else and passed on, the recipients should know 55 | that what they have is not the original version, so that the original 56 | author's reputation will not be affected by problems that might be 57 | introduced by others. 58 | 59 | Finally, software patents pose a constant threat to the existence of 60 | any free program. We wish to make sure that a company cannot 61 | effectively restrict the users of a free program by obtaining a 62 | restrictive license from a patent holder. Therefore, we insist that 63 | any patent license obtained for a version of the library must be 64 | consistent with the full freedom of use specified in this license. 65 | 66 | Most GNU software, including some libraries, is covered by the 67 | ordinary GNU General Public License. This license, the GNU Lesser 68 | General Public License, applies to certain designated libraries, and 69 | is quite different from the ordinary General Public License. We use 70 | this license for certain libraries in order to permit linking those 71 | libraries into non-free programs. 72 | 73 | When a program is linked with a library, whether statically or using 74 | a shared library, the combination of the two is legally speaking a 75 | combined work, a derivative of the original library. The ordinary 76 | General Public License therefore permits such linking only if the 77 | entire combination fits its criteria of freedom. The Lesser General 78 | Public License permits more lax criteria for linking other code with 79 | the library. 80 | 81 | We call this license the "Lesser" General Public License because it 82 | does Less to protect the user's freedom than the ordinary General 83 | Public License. It also provides other free software developers Less 84 | of an advantage over competing non-free programs. These disadvantages 85 | are the reason we use the ordinary General Public License for many 86 | libraries. However, the Lesser license provides advantages in certain 87 | special circumstances. 88 | 89 | For example, on rare occasions, there may be a special need to 90 | encourage the widest possible use of a certain library, so that it becomes 91 | a de-facto standard. To achieve this, non-free programs must be 92 | allowed to use the library. A more frequent case is that a free 93 | library does the same job as widely used non-free libraries. In this 94 | case, there is little to gain by limiting the free library to free 95 | software only, so we use the Lesser General Public License. 96 | 97 | In other cases, permission to use a particular library in non-free 98 | programs enables a greater number of people to use a large body of 99 | free software. For example, permission to use the GNU C Library in 100 | non-free programs enables many more people to use the whole GNU 101 | operating system, as well as its variant, the GNU/Linux operating 102 | system. 103 | 104 | Although the Lesser General Public License is Less protective of the 105 | users' freedom, it does ensure that the user of a program that is 106 | linked with the Library has the freedom and the wherewithal to run 107 | that program using a modified version of the Library. 108 | 109 | The precise terms and conditions for copying, distribution and 110 | modification follow. Pay close attention to the difference between a 111 | "work based on the library" and a "work that uses the library". The 112 | former contains code derived from the library, whereas the latter must 113 | be combined with the library in order to run. 114 | 115 | GNU LESSER GENERAL PUBLIC LICENSE 116 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 117 | 118 | 0. This License Agreement applies to any software library or other 119 | program which contains a notice placed by the copyright holder or 120 | other authorized party saying it may be distributed under the terms of 121 | this Lesser General Public License (also called "this License"). 122 | Each licensee is addressed as "you". 123 | 124 | A "library" means a collection of software functions and/or data 125 | prepared so as to be conveniently linked with application programs 126 | (which use some of those functions and data) to form executables. 127 | 128 | The "Library", below, refers to any such software library or work 129 | which has been distributed under these terms. A "work based on the 130 | Library" means either the Library or any derivative work under 131 | copyright law: that is to say, a work containing the Library or a 132 | portion of it, either verbatim or with modifications and/or translated 133 | straightforwardly into another language. (Hereinafter, translation is 134 | included without limitation in the term "modification".) 135 | 136 | "Source code" for a work means the preferred form of the work for 137 | making modifications to it. For a library, complete source code means 138 | all the source code for all modules it contains, plus any associated 139 | interface definition files, plus the scripts used to control compilation 140 | and installation of the library. 141 | 142 | Activities other than copying, distribution and modification are not 143 | covered by this License; they are outside its scope. The act of 144 | running a program using the Library is not restricted, and output from 145 | such a program is covered only if its contents constitute a work based 146 | on the Library (independent of the use of the Library in a tool for 147 | writing it). Whether that is true depends on what the Library does 148 | and what the program that uses the Library does. 149 | 150 | 1. You may copy and distribute verbatim copies of the Library's 151 | complete source code as you receive it, in any medium, provided that 152 | you conspicuously and appropriately publish on each copy an 153 | appropriate copyright notice and disclaimer of warranty; keep intact 154 | all the notices that refer to this License and to the absence of any 155 | warranty; and distribute a copy of this License along with the 156 | Library. 157 | 158 | You may charge a fee for the physical act of transferring a copy, 159 | and you may at your option offer warranty protection in exchange for a 160 | fee. 161 | 162 | 2. You may modify your copy or copies of the Library or any portion 163 | of it, thus forming a work based on the Library, and copy and 164 | distribute such modifications or work under the terms of Section 1 165 | above, provided that you also meet all of these conditions: 166 | 167 | a) The modified work must itself be a software library. 168 | 169 | b) You must cause the files modified to carry prominent notices 170 | stating that you changed the files and the date of any change. 171 | 172 | c) You must cause the whole of the work to be licensed at no 173 | charge to all third parties under the terms of this License. 174 | 175 | d) If a facility in the modified Library refers to a function or a 176 | table of data to be supplied by an application program that uses 177 | the facility, other than as an argument passed when the facility 178 | is invoked, then you must make a good faith effort to ensure that, 179 | in the event an application does not supply such function or 180 | table, the facility still operates, and performs whatever part of 181 | its purpose remains meaningful. 182 | 183 | (For example, a function in a library to compute square roots has 184 | a purpose that is entirely well-defined independent of the 185 | application. Therefore, Subsection 2d requires that any 186 | application-supplied function or table used by this function must 187 | be optional: if the application does not supply it, the square 188 | root function must still compute square roots.) 189 | 190 | These requirements apply to the modified work as a whole. If 191 | identifiable sections of that work are not derived from the Library, 192 | and can be reasonably considered independent and separate works in 193 | themselves, then this License, and its terms, do not apply to those 194 | sections when you distribute them as separate works. But when you 195 | distribute the same sections as part of a whole which is a work based 196 | on the Library, the distribution of the whole must be on the terms of 197 | this License, whose permissions for other licensees extend to the 198 | entire whole, and thus to each and every part regardless of who wrote 199 | it. 200 | 201 | Thus, it is not the intent of this section to claim rights or contest 202 | your rights to work written entirely by you; rather, the intent is to 203 | exercise the right to control the distribution of derivative or 204 | collective works based on the Library. 205 | 206 | In addition, mere aggregation of another work not based on the Library 207 | with the Library (or with a work based on the Library) on a volume of 208 | a storage or distribution medium does not bring the other work under 209 | the scope of this License. 210 | 211 | 3. You may opt to apply the terms of the ordinary GNU General Public 212 | License instead of this License to a given copy of the Library. To do 213 | this, you must alter all the notices that refer to this License, so 214 | that they refer to the ordinary GNU General Public License, version 2, 215 | instead of to this License. (If a newer version than version 2 of the 216 | ordinary GNU General Public License has appeared, then you can specify 217 | that version instead if you wish.) Do not make any other change in 218 | these notices. 219 | 220 | Once this change is made in a given copy, it is irreversible for 221 | that copy, so the ordinary GNU General Public License applies to all 222 | subsequent copies and derivative works made from that copy. 223 | 224 | This option is useful when you wish to copy part of the code of 225 | the Library into a program that is not a library. 226 | 227 | 4. You may copy and distribute the Library (or a portion or 228 | derivative of it, under Section 2) in object code or executable form 229 | under the terms of Sections 1 and 2 above provided that you accompany 230 | it with the complete corresponding machine-readable source code, which 231 | must be distributed under the terms of Sections 1 and 2 above on a 232 | medium customarily used for software interchange. 233 | 234 | If distribution of object code is made by offering access to copy 235 | from a designated place, then offering equivalent access to copy the 236 | source code from the same place satisfies the requirement to 237 | distribute the source code, even though third parties are not 238 | compelled to copy the source along with the object code. 239 | 240 | 5. A program that contains no derivative of any portion of the 241 | Library, but is designed to work with the Library by being compiled or 242 | linked with it, is called a "work that uses the Library". Such a 243 | work, in isolation, is not a derivative work of the Library, and 244 | therefore falls outside the scope of this License. 245 | 246 | However, linking a "work that uses the Library" with the Library 247 | creates an executable that is a derivative of the Library (because it 248 | contains portions of the Library), rather than a "work that uses the 249 | library". The executable is therefore covered by this License. 250 | Section 6 states terms for distribution of such executables. 251 | 252 | When a "work that uses the Library" uses material from a header file 253 | that is part of the Library, the object code for the work may be a 254 | derivative work of the Library even though the source code is not. 255 | Whether this is true is especially significant if the work can be 256 | linked without the Library, or if the work is itself a library. The 257 | threshold for this to be true is not precisely defined by law. 258 | 259 | If such an object file uses only numerical parameters, data 260 | structure layouts and accessors, and small macros and small inline 261 | functions (ten lines or less in length), then the use of the object 262 | file is unrestricted, regardless of whether it is legally a derivative 263 | work. (Executables containing this object code plus portions of the 264 | Library will still fall under Section 6.) 265 | 266 | Otherwise, if the work is a derivative of the Library, you may 267 | distribute the object code for the work under the terms of Section 6. 268 | Any executables containing that work also fall under Section 6, 269 | whether or not they are linked directly with the Library itself. 270 | 271 | 6. As an exception to the Sections above, you may also combine or 272 | link a "work that uses the Library" with the Library to produce a 273 | work containing portions of the Library, and distribute that work 274 | under terms of your choice, provided that the terms permit 275 | modification of the work for the customer's own use and reverse 276 | engineering for debugging such modifications. 277 | 278 | You must give prominent notice with each copy of the work that the 279 | Library is used in it and that the Library and its use are covered by 280 | this License. You must supply a copy of this License. If the work 281 | during execution displays copyright notices, you must include the 282 | copyright notice for the Library among them, as well as a reference 283 | directing the user to the copy of this License. Also, you must do one 284 | of these things: 285 | 286 | a) Accompany the work with the complete corresponding 287 | machine-readable source code for the Library including whatever 288 | changes were used in the work (which must be distributed under 289 | Sections 1 and 2 above); and, if the work is an executable linked 290 | with the Library, with the complete machine-readable "work that 291 | uses the Library", as object code and/or source code, so that the 292 | user can modify the Library and then relink to produce a modified 293 | executable containing the modified Library. (It is understood 294 | that the user who changes the contents of definitions files in the 295 | Library will not necessarily be able to recompile the application 296 | to use the modified definitions.) 297 | 298 | b) Use a suitable shared library mechanism for linking with the 299 | Library. A suitable mechanism is one that (1) uses at run time a 300 | copy of the library already present on the user's computer system, 301 | rather than copying library functions into the executable, and (2) 302 | will operate properly with a modified version of the library, if 303 | the user installs one, as long as the modified version is 304 | interface-compatible with the version that the work was made with. 305 | 306 | c) Accompany the work with a written offer, valid for at 307 | least three years, to give the same user the materials 308 | specified in Subsection 6a, above, for a charge no more 309 | than the cost of performing this distribution. 310 | 311 | d) If distribution of the work is made by offering access to copy 312 | from a designated place, offer equivalent access to copy the above 313 | specified materials from the same place. 314 | 315 | e) Verify that the user has already received a copy of these 316 | materials or that you have already sent this user a copy. 317 | 318 | For an executable, the required form of the "work that uses the 319 | Library" must include any data and utility programs needed for 320 | reproducing the executable from it. However, as a special exception, 321 | the materials to be distributed need not include anything that is 322 | normally distributed (in either source or binary form) with the major 323 | components (compiler, kernel, and so on) of the operating system on 324 | which the executable runs, unless that component itself accompanies 325 | the executable. 326 | 327 | It may happen that this requirement contradicts the license 328 | restrictions of other proprietary libraries that do not normally 329 | accompany the operating system. Such a contradiction means you cannot 330 | use both them and the Library together in an executable that you 331 | distribute. 332 | 333 | 7. You may place library facilities that are a work based on the 334 | Library side-by-side in a single library together with other library 335 | facilities not covered by this License, and distribute such a combined 336 | library, provided that the separate distribution of the work based on 337 | the Library and of the other library facilities is otherwise 338 | permitted, and provided that you do these two things: 339 | 340 | a) Accompany the combined library with a copy of the same work 341 | based on the Library, uncombined with any other library 342 | facilities. This must be distributed under the terms of the 343 | Sections above. 344 | 345 | b) Give prominent notice with the combined library of the fact 346 | that part of it is a work based on the Library, and explaining 347 | where to find the accompanying uncombined form of the same work. 348 | 349 | 8. You may not copy, modify, sublicense, link with, or distribute 350 | the Library except as expressly provided under this License. Any 351 | attempt otherwise to copy, modify, sublicense, link with, or 352 | distribute the Library is void, and will automatically terminate your 353 | rights under this License. However, parties who have received copies, 354 | or rights, from you under this License will not have their licenses 355 | terminated so long as such parties remain in full compliance. 356 | 357 | 9. You are not required to accept this License, since you have not 358 | signed it. However, nothing else grants you permission to modify or 359 | distribute the Library or its derivative works. These actions are 360 | prohibited by law if you do not accept this License. Therefore, by 361 | modifying or distributing the Library (or any work based on the 362 | Library), you indicate your acceptance of this License to do so, and 363 | all its terms and conditions for copying, distributing or modifying 364 | the Library or works based on it. 365 | 366 | 10. Each time you redistribute the Library (or any work based on the 367 | Library), the recipient automatically receives a license from the 368 | original licensor to copy, distribute, link with or modify the Library 369 | subject to these terms and conditions. You may not impose any further 370 | restrictions on the recipients' exercise of the rights granted herein. 371 | You are not responsible for enforcing compliance by third parties with 372 | this License. 373 | 374 | 11. If, as a consequence of a court judgment or allegation of patent 375 | infringement or for any other reason (not limited to patent issues), 376 | conditions are imposed on you (whether by court order, agreement or 377 | otherwise) that contradict the conditions of this License, they do not 378 | excuse you from the conditions of this License. If you cannot 379 | distribute so as to satisfy simultaneously your obligations under this 380 | License and any other pertinent obligations, then as a consequence you 381 | may not distribute the Library at all. For example, if a patent 382 | license would not permit royalty-free redistribution of the Library by 383 | all those who receive copies directly or indirectly through you, then 384 | the only way you could satisfy both it and this License would be to 385 | refrain entirely from distribution of the Library. 386 | 387 | If any portion of this section is held invalid or unenforceable under any 388 | particular circumstance, the balance of the section is intended to apply, 389 | and the section as a whole is intended to apply in other circumstances. 390 | 391 | It is not the purpose of this section to induce you to infringe any 392 | patents or other property right claims or to contest validity of any 393 | such claims; this section has the sole purpose of protecting the 394 | integrity of the free software distribution system which is 395 | implemented by public license practices. Many people have made 396 | generous contributions to the wide range of software distributed 397 | through that system in reliance on consistent application of that 398 | system; it is up to the author/donor to decide if he or she is willing 399 | to distribute software through any other system and a licensee cannot 400 | impose that choice. 401 | 402 | This section is intended to make thoroughly clear what is believed to 403 | be a consequence of the rest of this License. 404 | 405 | 12. If the distribution and/or use of the Library is restricted in 406 | certain countries either by patents or by copyrighted interfaces, the 407 | original copyright holder who places the Library under this License may add 408 | an explicit geographical distribution limitation excluding those countries, 409 | so that distribution is permitted only in or among countries not thus 410 | excluded. In such case, this License incorporates the limitation as if 411 | written in the body of this License. 412 | 413 | 13. The Free Software Foundation may publish revised and/or new 414 | versions of the Lesser General Public License from time to time. 415 | Such new versions will be similar in spirit to the present version, 416 | but may differ in detail to address new problems or concerns. 417 | 418 | Each version is given a distinguishing version number. If the Library 419 | specifies a version number of this License which applies to it and 420 | "any later version", you have the option of following the terms and 421 | conditions either of that version or of any later version published by 422 | the Free Software Foundation. If the Library does not specify a 423 | license version number, you may choose any version ever published by 424 | the Free Software Foundation. 425 | 426 | 14. If you wish to incorporate parts of the Library into other free 427 | programs whose distribution conditions are incompatible with these, 428 | write to the author to ask for permission. For software which is 429 | copyrighted by the Free Software Foundation, write to the Free 430 | Software Foundation; we sometimes make exceptions for this. Our 431 | decision will be guided by the two goals of preserving the free status 432 | of all derivatives of our free software and of promoting the sharing 433 | and reuse of software generally. 434 | 435 | NO WARRANTY 436 | 437 | 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO 438 | WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. 439 | EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR 440 | OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY 441 | KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE 442 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 443 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE 444 | LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME 445 | THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 446 | 447 | 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN 448 | WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY 449 | AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU 450 | FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR 451 | CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE 452 | LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING 453 | RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A 454 | FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF 455 | SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH 456 | DAMAGES. 457 | 458 | END OF TERMS AND CONDITIONS 459 | 460 | How to Apply These Terms to Your New Libraries 461 | 462 | If you develop a new library, and you want it to be of the greatest 463 | possible use to the public, we recommend making it free software that 464 | everyone can redistribute and change. You can do so by permitting 465 | redistribution under these terms (or, alternatively, under the terms of the 466 | ordinary General Public License). 467 | 468 | To apply these terms, attach the following notices to the library. It is 469 | safest to attach them to the start of each source file to most effectively 470 | convey the exclusion of warranty; and each file should have at least the 471 | "copyright" line and a pointer to where the full notice is found. 472 | 473 | 474 | Copyright (C) 475 | 476 | This library is free software; you can redistribute it and/or 477 | modify it under the terms of the GNU Lesser General Public 478 | License as published by the Free Software Foundation; either 479 | version 2.1 of the License, or (at your option) any later version. 480 | 481 | This library is distributed in the hope that it will be useful, 482 | but WITHOUT ANY WARRANTY; without even the implied warranty of 483 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 484 | Lesser General Public License for more details. 485 | 486 | You should have received a copy of the GNU Lesser General Public 487 | License along with this library; if not, write to the Free Software 488 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 489 | USA 490 | 491 | Also add information on how to contact you by electronic and paper mail. 492 | 493 | You should also get your employer (if you work as a programmer) or your 494 | school, if any, to sign a "copyright disclaimer" for the library, if 495 | necessary. Here is a sample; alter the names: 496 | 497 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 498 | library `Frob' (a library for tweaking knobs) written by James Random 499 | Hacker. 500 | 501 | , 1 April 1990 502 | Ty Coon, President of Vice 503 | 504 | That's all there is to it! 505 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BIMbots-FreeCAD 2 | A FreeCAD plugin to communicate with BIMbots services - http://bimbots.org/ 3 | 4 | **Warning - the BIMbots service has been retired and this addon is now obsolete** 5 | 6 | ![](doc/images/bimbots-ui-01.jpg) 7 | 8 | This FreeCAD plugin allows a user to: 9 | 1. Upload a FreeCAD model or selected parts of a FreeCAD model to a BIMBots instance (usually a [BIMServer](http://bimserver.org/) with external services enabled) 10 | 2. Perform different services and analyses on their model 11 | 3. Read said results in FreeCAD (usually in the form of a text report) or a BCF file (not yet supported - see below) 12 | 13 | This plugin is written in Python and consists of a single all-in-one Python file along with a companion FreeCAD macro for convenience. 14 | It can be used in several ways: 15 | 16 | ### Run BIMBots within FreeCAD 17 | * The main usage is to work within [FreeCAD](https://www.freecadweb.org) and be simply launched as a macro. 18 | * If you have the [BIM Workbench](https://github.com/yorikvanhavre/BIM_Workbench) installed, this BIMBots plugin will be automatically detected at start and you will find a **BIMBots** command under **Utils** menu. 19 | 20 | ### Run BIMBots from the CLI 21 | The BIMBots plugin can also be run directly from the command line terminal, in which case it prints a list of services it was able to reach, or imported as a python module (Python 2 and Python 3 compatible), in which case you have access to several utility functions to retrieve and communicate with BIMbots services. So essentiall this can also be used as a library to build your own BIMBots client. 22 | 23 | Check the [API documentation](doc/documentation.md) page (autogenerated with [pdoc](https://pdoc3.github.io/pdoc/)) and the [FreeCAD GUI documentation](doc/ui-documentation.md). 24 | 25 | ### How to install 26 | In FreeCAD, just head to menu **Tools -> Addons Manager**, locate the BIMBots addon, press the **Install** button, and restart FreeCAD. 27 | 28 | ### How to use 29 | Once installed, you will find a **BIMBots** entry under menu **Macro -> Macros**. If you have the [BIM Workbench](https://github.com/yorikvanhavre/BIM_Workbench) also installed, the BIMBots plugin will be automatically detected at start and you will find a **BIMBots** command under menu **Utils**. 30 | Refer to the [documentation](doc/ui-documentation.md) for complete use instructions. 31 | 32 | ### Features 33 | 34 | #### When used as a Python module, it can: 35 | 36 | * Retrive a list of BIMbots services 37 | * Authenticate with any of the services 38 | * Keep authentication credentials in a config file 39 | * Test services (send a minimal test IFC file that is guaranteed to work) 40 | * Send actual IFC files 41 | * Get the results 42 | 43 | #### When running inside FreeCAD: 44 | 45 | * All functionality is available from the GUI 46 | * Auto-discover available services 47 | * Add/remove custom servers 48 | * Authenticate with services 49 | * Send model data to any service 50 | * Display JSON or text reports 51 | * Double-click results (JSON results only) to select corresponding objects in the 3D view 52 | 53 | #### To do (help welcome!): 54 | 55 | * Handle Context-Id (reuse an already sent model slot) 56 | * Handle asynchronous connection (don't wait and freeze the FreeCAD interface while data is being transmitted) 57 | * Implement display of BCF files in FreeCAD (in progess - Part of a [GSOC](https://forum.freecadweb.org/viewtopic.php?f=8&t=35465) project) 58 | 59 | #### Quick how-to use from Python 60 | 61 | ``` 62 | >>> import bimbots 63 | >>> bimbots.get_service_providers() 64 | ``` 65 | 66 | This returns a dictionary containing the different service providers found (both auto-discovered and manually added via the FreeCAD UI): 67 | 68 | ``` 69 | [{u'listUrl': u'https://ifcanalysis.bimserver.services/servicelist', u'name': u'ifcanalyses'}, 70 | {u'listUrl': u'http://localhost:8080/servicelist', u'name': u'Default localdev BIMserver', 71 | u'description': u'Default localdev BIMserver'}, {u'listUrl': u'http://localhost:8081/servicelist', 72 | u'name': u'2nd localdev BIMserver', u'description': u'2nd localdev BIMserver'}, 73 | {u'listUrl': u'http://localhost:8082/servicelist', u'name': u'Default JAR runner', 74 | u'description': u'Default JAR runner'}, {u'listUrl': u'https://thisisanexperimentalserver.com/servicelist', 75 | u'name': u'Experimentalserver.com', u'description': u'Experimental BIMserver'}] 76 | ``` 77 | Then, using one of the "listUrl" above: 78 | 79 | `>>> bimbots.get_services('http://localhost:8082/servicelist')` 80 | 81 | This returns a list of services offered by the given server (adding the /servicelist is optional): 82 | 83 | ``` 84 | [{u'inputs': [u'IFC_STEP_2X3TC1'], u'resourceUrl': u'http://localhost:8082/services', 85 | u'description': u'IFC Analytics Service', u'outputs': [u'IFC_ANALYTICS_JSON_1_0'], 86 | u'providerIcon': u'/img/bimserver.png', u'provider': u"Yorik's test BIMserver", 87 | u'oauth': {u'tokenUrl': u'http://localhost:8082/oauth/access', 88 | u'registerUrl': u'http://localhost:8082/oauth/register', 89 | u'authorizationUrl': u'http://localhost:8082/oauth/authorize'}, 90 | u'id': 2097206, u'name': u'IFC Analytics Service'}, 91 | {u'inputs': [u'IFC_STEP_2X3TC1'], u'resourceUrl': u'http://localhost:8082/services', 92 | u'description': u'BIMserver plugin that provides an analysis of a model and and outputs it into json', 93 | u'outputs': [u'UNSTRUCTURED_UTF8_TEXT_1_0'], u'providerIcon': u'/img/bimserver.png', 94 | u'provider': u"Yorik's test BIMserver", 95 | u'oauth': {u'tokenUrl': u'http://localhost:8082/oauth/access', 96 | u'registerUrl': u'http://localhost:8082/oauth/register', 97 | u'authorizationUrl': u'http://localhost:8082/oauth/authorize'}, 98 | u'id': 2162742, u'name': u'Simple Analyses Service'}] 99 | ``` 100 | The next step, if you want to use a service, is to authenticate with it. This is done in two steps. Step 1 is done using one of the "registeUrl" above: 101 | 102 | `>>> bimbots.authenticate_step_1('http://localhost:8082/oauth/register')` 103 | 104 | This will return a dict with keys that identify our BIMbots plugin on the given server. 105 | 106 | `>>> bimbots.authenticate_step_2('http://localhost:8082/oauth/authorize', 'freecad', 'Simple Analyses Service')` 107 | 108 | Client_id and service_name will be returned by the step 1 above. This will pop up a browser window, which will access the given BIMbots server, on which you must have a valid user already logged in. You will land on a page that asks you to confirm. Upon confirmation, you will receive a token and an url. Save them to your config file with: 109 | 110 | `>>> bimbots.save_authentication('http://localhost:8082/', 2097206, 'Simple Analyses Service', service_url, token)` 111 | 112 | The first URL is used to bind this service to a given server in the config file. You can use either the server name, as I did above, or the /servicelist URL obtained by the first step above in these instructions. service_id, service_name are obtained in step 1 above, and service_url, token are obtained via the web interface opened in step 2. 113 | 114 | After you properly registered the service, you can now try sending it a test file: 115 | 116 | `send_test_payload('http://localhost:8082/', 2097206)` 117 | 118 | There are more functions to interact with services. Check the [API documentation](doc/documentation.md) for the full list of available functions. 119 | -------------------------------------------------------------------------------- /bimbots.FCMacro: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | #*************************************************************************** 5 | #* * 6 | #* Copyright (c) 2019 Yorik van Havre * 7 | #* * 8 | #* This program is free software; you can redistribute it and/or modify * 9 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 10 | #* as published by the Free Software Foundation; either version 2 of * 11 | #* the License, or (at your option) any later version. * 12 | #* for detail see the LICENCE text file. * 13 | #* * 14 | #* This program is distributed in the hope that it will be useful, * 15 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 16 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 17 | #* GNU Library General Public License for more details. * 18 | #* * 19 | #* You should have received a copy of the GNU Library General Public * 20 | #* License along with this program; if not, write to the Free Software * 21 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 22 | #* USA * 23 | #* * 24 | #*************************************************************************** 25 | 26 | # Homepage: https://github.com/opensourceBIM/BIMbots-FreeCAD 27 | 28 | """This is a short macro for FreeCAD meant to be used by the addon installer""" 29 | 30 | import bimbots 31 | bimbots.launch_ui() 32 | -------------------------------------------------------------------------------- /bimbots.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | #*************************************************************************** 5 | #* * 6 | #* Copyright (c) 2019 Yorik van Havre * 7 | #* * 8 | #* This program is free software; you can redistribute it and/or modify * 9 | #* it under the terms of the GNU Lesser General Public License (LGPL) * 10 | #* as published by the Free Software Foundation; either version 2 of * 11 | #* the License, or (at your option) any later version. * 12 | #* for detail see the LICENCE text file. * 13 | #* * 14 | #* This program is distributed in the hope that it will be useful, * 15 | #* but WITHOUT ANY WARRANTY; without even the implied warranty of * 16 | #* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * 17 | #* GNU Library General Public License for more details. * 18 | #* * 19 | #* You should have received a copy of the GNU Library General Public * 20 | #* License along with this program; if not, write to the Free Software * 21 | #* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * 22 | #* USA * 23 | #* * 24 | #*************************************************************************** 25 | 26 | # Homepage: https://github.com/opensourceBIM/BIMbots-FreeCAD 27 | # That repo contains useful implementation notes too 28 | 29 | """This module provides tools to communicate with BIMbots services, and 30 | a FreeCAD GUI, that autoruns if this module is executed as a FreeCAD macro or 31 | simply imported from the FreeCAD Python console. If run from the command line, 32 | it prints a summary of available (and reachable) BIMbots services. 33 | 34 | See also the [GUI documentation](ui-documentation.md) for GUI usage inside FreeCAD""" 35 | 36 | from __future__ import print_function # this code is compatible with python 2 and 3 37 | 38 | import os 39 | import sys 40 | import tempfile 41 | import requests 42 | import json 43 | 44 | # python2 / python3 compatibility tweaks 45 | if sys.version_info.major < 3: 46 | # Python 2 47 | import urllib 48 | from urllib import urlencode 49 | def tostr(something): 50 | "a convenience function to unify py2/py3 conversion to string" 51 | return unicode(something) 52 | else: 53 | # Python 3 54 | import urllib.parse 55 | from urllib.parse import urlencode 56 | def tostr(something): 57 | "a convenience function to unify py2/py3 conversion to string" 58 | return str(something) 59 | 60 | 61 | ############# Configuration defaults 62 | 63 | 64 | # the following values are not written in the config file: 65 | CONFIG_FILE = os.path.join(os.path.expanduser("~"),".BIMbots") # A file to store authentication tokens 66 | if sys.platform.lower().startswith("win"): 67 | CONFIG_FILE = os.path.join(os.environ['APPDATA'], 'BIMbots.cfg') # use something nicer on windows 68 | DEBUG = False # If True, debug messages are printed, and test items are added to the UI. If not, everything happens (and fails) silently 69 | DECAMELIZE = True # if True, variable names appear de-camelized in results 70 | 71 | # the following values can be overwritten in the config file: 72 | SERVICES_URL = "https://raw.githubusercontent.com/opensourceBIM/BIMserver-Repository/master/serviceproviders.json" 73 | CONNECTION_TIMEOUT = 5 # connection timeout, in seconds 74 | CLIENT_NAME = "FreeCAD" 75 | CLIENT_DESCRIPTION = "The FreeCAD BIMbots plugin" 76 | CLIENT_URL = "https://github.com/opensourceBIM/BIMbots-FreeCAD" 77 | CLIENT_ICON = "https://www.freecadweb.org/images/logo.png" #bimserver doesn't seem to like this image... Why, OH WHY? 78 | 79 | # detect if we're running inside FreeCAD 80 | try: 81 | import FreeCAD 82 | except: 83 | def translate(ctx,txt): 84 | return txt 85 | else: 86 | if FreeCAD.GuiUp: 87 | import FreeCADGui 88 | from PySide import QtCore 89 | from PySide import QtGui 90 | from DraftTools import translate 91 | 92 | 93 | ############# Config file management 94 | 95 | 96 | def read_config(): 97 | 98 | "Reads the config file, if found, and returns a dict of its contents" 99 | 100 | # Structure of the config file. It's a json file: 101 | # 102 | # { "config" : 103 | # { 104 | # "default_services_url": "https://server.com/serviceproviders.json", # a json giving urls of service providers 105 | # "connection_timeout": 5, # the timeout when trying to connect to online services 106 | # "client_name": "FreeCAD", # the name under which this application will be known by BIMServers 107 | # "client_description": "The best BIM app out there", # a description shown on BIMServers authentication pages and user settings 108 | # "client_icon": "https://server.com/image.png", # a PNG icon for this application 109 | # "client_url": "https://myserver.comg", # a URL for this application, shown on BIMservers 110 | # }, 111 | # "providers" : 112 | # [ 113 | # { "listUrl": "http://myserver.com/servicelist", # the list of services provided by the server 114 | # "name": "My Server", # a custom name for this server 115 | # }, ... 116 | # ] 117 | # "services" : 118 | # [ 119 | # { "id": 3014734, # the id number of the service, returned by get_services(). Warning, this is an int, not a string 120 | # "name": "IFC Analytics Service" # the name of the service, returned by get_services() 121 | # "provider_url": "http://localhost:8082/services", # the listUrl of the server returned by get_service_providers (ie. one by server) 122 | # "service_url": "http://localhost:8082/services/3014734", # the specific URL given by the auth procedure. Only present if authenticated 123 | # "token": "XXXXXXXXXXX", # the token given by the auth procedure. Only present if authenticated 124 | # }, ... 125 | # ] 126 | # } 127 | 128 | if os.path.exists(CONFIG_FILE): 129 | with open(CONFIG_FILE) as json_file: 130 | if DEBUG: 131 | print("Reading config from",CONFIG_FILE) 132 | data = json.load(json_file) 133 | return data 134 | elif DEBUG: 135 | print("No config file found at",CONFIG_FILE) 136 | return {'config':{},'services':[]} 137 | 138 | 139 | def get_config_value(value): 140 | 141 | "Returns the given config value from the config file, or defaults to the default value if existing" 142 | 143 | config = read_config() 144 | if value in config['config']: 145 | return config['config'][value] 146 | elif value.upper() in globals(): 147 | return globals()[value.upper()] 148 | else: 149 | if DEBUG: 150 | print("Value",value,"not found in config or default settings") 151 | return None 152 | 153 | 154 | def save_config(config): 155 | 156 | "Saves the given dict to the config file. Overwrites everything, be careful! Returns nothing." 157 | 158 | if os.path.exists(CONFIG_FILE) and DEBUG: 159 | print("Config file",CONFIG_FILE,"not found. Creating a new one.") 160 | with open(CONFIG_FILE, 'w') as json_file: 161 | json.dump(config, json_file) 162 | if DEBUG: 163 | print("Saved config to",CONFIG_FILE) 164 | 165 | 166 | def save_authentication(provider_url,service_id,service_name,service_url,token): 167 | 168 | "Saved the given service authentication data to the config file. Returns nothing." 169 | 170 | config = read_config() 171 | for service in config['services']: 172 | if (service['provider_url'] == provider_url) and (service['id'] == service_id): 173 | service['name'] = service_name 174 | service['service_url'] = service_url 175 | service['token'] = token 176 | break 177 | else: 178 | data = { 179 | "provider_url": provider_url, 180 | "id": service_id, 181 | "name": service_name, 182 | "service_url": service_url, 183 | "token": token 184 | } 185 | config['services'].append(data) 186 | if DEBUG: 187 | print("Saving config:",config) 188 | save_config(config) 189 | 190 | 191 | def get_service_config(provider_url,service_id): 192 | 193 | "Returns the service associated with the given provider url and service id if it has already been authenticated" 194 | 195 | data = read_config() 196 | for service in data['services']: 197 | if (service['provider_url'] == provider_url) and (service['id'] == service_id): 198 | if 'token' in service: 199 | return service 200 | return None 201 | 202 | 203 | def save_default_config(): 204 | 205 | "Saves the default settings to the config file. Returns nothing" 206 | 207 | config = read_config() 208 | for setting in ["default_services_url","connection_timeout","client_name","client_description","client_icon","client_url"]: 209 | config['config'][setting] = globals()[setting.upper()] 210 | save_config(config) 211 | 212 | 213 | def get_custom_providers(): 214 | 215 | "Returns custom providers from the config file" 216 | 217 | providers = [] 218 | config = read_config() 219 | if "providers" in config: 220 | for provider in config['providers']: 221 | provider['custom'] = "true" # indicate that this provider is custom 222 | providers.append(provider) 223 | return providers 224 | 225 | 226 | def save_custom_provider(name,list_url): 227 | 228 | "Saves a custom services provider to the config file. Returns nothing." 229 | 230 | config = read_config() 231 | if "providers" in config: 232 | for provider in config['providers']: 233 | if provider['listUrl'] == list_url: 234 | provider['name'] = name 235 | break 236 | else: 237 | config['providers'].append({'name':name,'listUrl':list_url}) 238 | else: 239 | config['providers'] = [{'name':name,'listUrl':list_url}] 240 | save_config(config) 241 | 242 | 243 | def delete_custom_provider(list_url): 244 | 245 | "Removes a custom provider from the config file. Returns nothing." 246 | 247 | config = read_config() 248 | if "providers" in config: 249 | providers = [] 250 | found = False 251 | for provider in config['providers']: 252 | if provider['listUrl'] == list_url: 253 | found = True 254 | else: 255 | providers.append(provider) 256 | if found: 257 | config['providers'] = providers 258 | save_config(config) 259 | return 260 | if DEBUG: 261 | print("Error: Provider not found in config:",list_url) 262 | 263 | 264 | ############# Generic BIMbots interface - doesn't depend on FreeCAD 265 | 266 | 267 | def get_service_providers(autodiscover=True,url=None): 268 | 269 | """Returns a list of dicts {name,desciption,listUrl} of BIMbots services obtained from the stored config and, 270 | if autodiscover is True, from the given service list url (or from the default one if none is given).""" 271 | 272 | if not url: 273 | url = get_config_value("default_services_url") 274 | providers = get_custom_providers() 275 | if not autodiscover: 276 | return providers 277 | try: 278 | response = requests.get(url,timeout=get_config_value("connection_timeout")) 279 | except: 280 | if DEBUG: 281 | print("Error: unable to connect to service providers list at",url) 282 | return providers 283 | if response.ok: 284 | try: 285 | defaults = response.json()['active'] 286 | for default in defaults: 287 | for custom in providers: 288 | if custom['listUrl'] == default['listUrl']: 289 | break 290 | else: 291 | providers.append(default) 292 | return providers 293 | except: 294 | if DEBUG: 295 | print("Error: unable to read service providers list from",url) 296 | return providers 297 | else: 298 | if DEBUG: 299 | print("Error: unable to fetch service providers list from",url) 300 | return providers 301 | 302 | 303 | def get_services(list_url): 304 | 305 | "Returns a list of dicts of service plugins available from a given service provider list url" 306 | 307 | try: 308 | response = requests.get(list_url,timeout=get_config_value("connection_timeout")) 309 | except: 310 | if DEBUG: 311 | print("Error: unable to connect to service provider at",list_url) 312 | return [] 313 | if response.ok: 314 | try: 315 | return response.json()['services'] 316 | except: 317 | if list_url.endswith("servicelist"): 318 | if DEBUG: 319 | print("Error: unable to read services list from",list_url) 320 | return [] 321 | else: 322 | # try again adding /servicelist to the URL. Users might have saved just the server URL 323 | # and we don't want to bother them with petty details... 324 | if list_url.endswith("/"): 325 | return get_services(list_url+"servicelist") 326 | else: 327 | return get_services(list_url+"/servicelist") 328 | else: 329 | if DEBUG: 330 | print("Error: unable to fetch services list from",list_url) 331 | return [] 332 | 333 | 334 | def authenticate_step_1(register_url): 335 | 336 | "Sends an authentication request to the given server. Returns the result json as a dict" 337 | 338 | data = { 339 | "redirect_url": "SHOW_CODE", 340 | "client_name": get_config_value("client_name"), 341 | "client_description": get_config_value("client_description"), 342 | "client_icon": get_config_value("client_icon"), 343 | "client_url": get_config_value("client_url"), 344 | "type": "pull" 345 | } 346 | 347 | try: 348 | # using json= instead of data= provides headers automatically 349 | response = requests.post(url=register_url,json=data) 350 | except: 351 | if DEBUG: 352 | print("Error: unable to send authentication request for ",register_url) 353 | return None 354 | if response.ok: 355 | try: 356 | return response.json() 357 | except: 358 | if DEBUG: 359 | print("Error: unable to read authentication data from",register_url) 360 | return None 361 | else: 362 | if DEBUG: 363 | print("Error: unable to fetch authentication data from",register_url) 364 | return None 365 | 366 | 367 | def authenticate_step_2(authorization_url,client_id,service_name): 368 | 369 | "Opens the authorization url in an external browser. Returns True if successful, the authorization URL to be shown if not." 370 | 371 | 372 | data = { 373 | "auth_type": "service", 374 | "client_id": client_id, 375 | "response_type": "code", 376 | "redirect_uri": "SHOW_CODE", 377 | "state": "{ \"_serviceName\" : \"" + service_name + "\" }" 378 | } 379 | url = authorization_url + "?" + urlencode(data) 380 | try: 381 | import webbrowser 382 | except: 383 | print("Error: Unable to launch web browser. Please paste the following URL in your web browser:") 384 | print(url) 385 | return url 386 | else: 387 | res = webbrowser.open(url) 388 | if res: 389 | return True 390 | else: 391 | return url 392 | 393 | 394 | def print_services(): 395 | 396 | "Prints a list of available (and reachable) services" 397 | 398 | found = False 399 | providers = get_service_providers() 400 | for provider in providers: 401 | services = get_services(provider['listUrl']) 402 | for service in services: 403 | if not found: 404 | print("Available services:") 405 | found = True 406 | authenticated = get_service_config(provider['listUrl'],service['id']) 407 | if authenticated: 408 | print("Service",service['name'],"from",service['provider'],"- authenticated as",authenticated['provider_url'],", service",authenticated['id']) 409 | else: 410 | print("Service",service['name'],"from",service['provider'],"- not authenticated") 411 | if not found: 412 | print("No available service found") 413 | 414 | 415 | def beautyprint(data): 416 | 417 | "Beautifies (prints with indents) and prints a given dictionary or json string" 418 | 419 | if isinstance(data,(dict,list)): 420 | data = json.dumps(data,sort_keys = True,indent=4) 421 | else: 422 | # this is a string already, convert/deconvert to get indents 423 | data = json.dumps(json.loads(data),sort_keys = True,indent=4) 424 | print(data) 425 | 426 | 427 | def send_ifc_payload(provider_url,service_id,file_path): 428 | 429 | "Sends a given IFC file to the given service. Returns the json response as a dict" 430 | 431 | service = get_service_config(provider_url,service_id) 432 | if DEBUG: 433 | print("Sending",file_path,"to",provider_url,"service",service_id,"...") 434 | if service: 435 | headers = { 436 | "Input-Type": "IFC_STEP_2X3TC1", 437 | "Token": service['token'], 438 | "Accept-Flow": "SYNC,ASYNC_WS" # preferred workflow - To be tested 439 | } 440 | #if "Context-Id" in service: # this model has already been uploaded before. TODO - This should be stored per model, not per service 441 | # headers['Context-Id'] = service['Context-Id'] 442 | if os.path.exists(file_path): 443 | with open(file_path) as file_stream: 444 | data = file_stream.read() 445 | else: 446 | if DEBUG: 447 | print("Error: unable to load payload IFC file from",file_path,". Aborting") 448 | return {} 449 | try: 450 | response = requests.post(service['service_url'],headers=headers,data=data,timeout=get_config_value("connection_timeout")) 451 | except: 452 | if DEBUG: 453 | print("Error: unable to connect to service provider at",service['service_url']) 454 | return {} 455 | if response.ok: 456 | try: 457 | res = response.json() 458 | # TODO get headers too, get Context-Id 459 | except: 460 | try: 461 | text = response.content 462 | except: 463 | if DEBUG: 464 | print(response) 465 | print("Error: unable to read response from service",service_id,"at",service['service_url']) 466 | return None 467 | else: 468 | return text 469 | else: 470 | if ("message" in res) and ("error" in res['message'].lower()) and ("code" in res): 471 | print("This payload has been rejected by the server, with the following error: Error code",res['code'],":",res['message']) 472 | return {} 473 | return res 474 | else: 475 | if DEBUG: 476 | print("Error: unable to fetch response from service",service_id,"at",service['service_url']) 477 | return {} 478 | else: 479 | if DEBUG: 480 | print("No authentication token found for this service. Aborting.") 481 | return {} 482 | 483 | 484 | def send_test_payload(provider_url,service_id): 485 | 486 | "Sends a test IFC file to the given service. Returns the json response as a dict" 487 | 488 | payload_file = os.path.join(os.path.dirname(__file__),"testfiles","test payload.ifc") 489 | return send_ifc_payload(provider_url,service_id,payload_file) 490 | 491 | def get_plugin_info(): 492 | 493 | "Returns a dict with info about this plugin, usable for ex. by FreeCAD" 494 | 495 | return {'Pixmap' : os.path.join(os.path.dirname(__file__),"icons","BIM-Bots-validationchecker.png"), 496 | 'MenuText': "BIMBots", 497 | 'ToolTip' : "Launches the BIMBots tool"} 498 | 499 | 500 | ############# FreeCAD UI panel 501 | 502 | 503 | def launch_ui(): 504 | 505 | "Opens the BIMbots task panel in FreeCAD" 506 | 507 | FreeCADGui.Control.showDialog(bimbots_panel()) 508 | 509 | 510 | class bimbots_panel: 511 | 512 | """This is the interface panel implementation of bimbots.ui. It is meant to run inside FreeCAD. 513 | It is launched by the launch_ui() function""" 514 | 515 | def __init__(self): 516 | 517 | # this is to be able to cancel running progress 518 | self.running = True 519 | 520 | # locate and load available translations 521 | FreeCADGui.addLanguagePath(os.path.join(os.path.dirname(__file__),"translations")) 522 | 523 | # load the ui file. Widgets re automatically named from the ui file 524 | self.form = FreeCADGui.PySideUic.loadUi(os.path.join(os.path.dirname(__file__),"bimbots.ui")) 525 | 526 | # set the logo and icon 527 | self.form.setWindowIcon(QtGui.QIcon(os.path.join(os.path.dirname(__file__),"icons","BIM-Bots-validationchecker.png"))) 528 | logo = QtGui.QPixmap(os.path.join(os.path.dirname(__file__),"icons","BIM-Bots-header.png")) 529 | w = self.form.servicesList.width() 530 | h = int(w*0.2578) 531 | self.form.labelLogo.setText("") 532 | self.form.labelLogo.setPixmap(logo.scaled(w,h)) 533 | # hide the logo for now TODO : Do something better here... 534 | self.form.labelLogo.hide() 535 | 536 | # hide the collapsible parts 537 | self.form.groupProgress.hide() 538 | self.form.groupRescan.hide() 539 | self.form.groupAddService.hide() 540 | self.form.groupAuthenticate.hide() 541 | self.form.groupResults.hide() 542 | 543 | # restore default settings 544 | self.get_defaults() 545 | 546 | # connect buttons 547 | self.form.buttonRescan.clicked.connect(self.form.groupRescan.show) 548 | self.form.buttonDoRescan.clicked.connect(self.form.groupRescan.hide) 549 | self.form.buttonDoRescan.clicked.connect(self.on_scan) 550 | self.form.buttonCancelRescan.clicked.connect(self.form.groupRescan.hide) 551 | self.form.buttonAddService.clicked.connect(self.form.groupAddService.show) 552 | self.form.buttonRemoveService.clicked.connect(self.on_remove_service) 553 | self.form.buttonSaveService.clicked.connect(self.on_add_service) 554 | self.form.buttonCancelService.clicked.connect(self.form.groupAddService.hide) 555 | self.form.buttonAuthenticate.clicked.connect(self.form.groupAuthenticate.show) 556 | self.form.buttonAuthenticate.clicked.connect(self.on_authenticate) 557 | self.form.buttonSaveAuthenticate.clicked.connect(self.on_save_authenticate) 558 | self.form.buttonCancelAuthenticate.clicked.connect(self.form.groupAuthenticate.hide) 559 | self.form.buttonRun.clicked.connect(self.on_run) 560 | self.form.buttonCancelProgress.clicked.connect(self.on_cancel) 561 | self.form.buttonCloseResults.clicked.connect(self.form.groupServices.show) 562 | self.form.buttonCloseResults.clicked.connect(self.form.groupRun.show) 563 | self.form.buttonCloseResults.clicked.connect(self.form.groupResults.hide) 564 | 565 | # fields validation 566 | self.form.lineEditServiceName.textChanged.connect(self.validate_fields) 567 | self.form.lineEditServiceUrl.textChanged.connect(self.validate_fields) 568 | self.form.lineEditAuthenticateUrl.textChanged.connect(self.validate_fields) 569 | self.form.lineEditAuthenticateToken.textChanged.connect(self.validate_fields) 570 | 571 | # connect services list 572 | self.form.servicesList.currentItemChanged.connect(self.on_list_click) 573 | self.form.scopeList.currentItemChanged.connect(self.on_list_click) 574 | 575 | # connect widgets that should remember their setting 576 | self.form.checkAutoDiscover.stateChanged.connect(self.save_defaults) 577 | self.form.checkShowUnreachable.stateChanged.connect(self.save_defaults) 578 | 579 | # connect clickable links 580 | self.form.treeResults.itemDoubleClicked.connect(self.on_click_results) 581 | self.form.labelHelp.linkActivated.connect(self.on_click_help) 582 | 583 | # perform initial scan after the UI has been fully drawn 584 | QtCore.QTimer.singleShot(0,self.on_scan) 585 | 586 | # remove test items if needed 587 | if not DEBUG: 588 | self.form.scopeList.takeItem(5) # Test output only 589 | self.form.scopeList.takeItem(4) # Test payload 590 | self.form.scopeList.takeItem(3) # External IFC file 591 | 592 | def getStandardButtons(self): 593 | 594 | "The list of buttons to show above the task panel. Returns a Close button only." 595 | 596 | return int(QtGui.QDialogButtonBox.Close) 597 | 598 | def needsFullSpace(self): 599 | 600 | "Notifies FreeCAD that this panel needs max height. Returns True" 601 | 602 | return True 603 | 604 | def reject(self): 605 | 606 | """Called when the "Close" button of the task dialog is pressed, closes the panel. Returns nothing""" 607 | 608 | FreeCADGui.Control.closeDialog() 609 | if FreeCAD.ActiveDocument: 610 | FreeCAD.ActiveDocument.recompute() 611 | 612 | def on_scan(self): 613 | 614 | "Scans for providers and services and updates the Available Services list. Returns nothing" 615 | 616 | # clean the services list 617 | self.form.servicesList.clear() 618 | 619 | # setup the progress bar 620 | self.running = True 621 | self.form.groupProgress.show() 622 | self.form.progressBar.setFormat(translate("BIMBots","Getting services")) 623 | 624 | # query services 625 | providers = get_service_providers(autodiscover=self.form.checkAutoDiscover.isChecked()) 626 | self.form.progressBar.setValue(int(100/len(providers))) 627 | n = 1 628 | for provider in providers: 629 | top = QtGui.QTreeWidgetItem(self.form.servicesList) 630 | top.setText(0,provider['name']) 631 | top.setIcon(0,QtGui.QIcon(os.path.join(os.path.dirname(__file__),"icons","Tango-Computer.svg"))) 632 | # store the whole provider dict 633 | top.setData(0,QtCore.Qt.UserRole,json.dumps(provider)) 634 | top.setToolTip(0,provider['name']) 635 | if "description" in provider: 636 | if provider['description']: 637 | top.setToolTip(0,provider['description']) 638 | if "custom" in provider: 639 | top.setToolTip(0,top.toolTip(0)+" ("+translate("BIMBots","saved")+")") 640 | else: 641 | top.setToolTip(0,top.toolTip(0)+" ("+translate("BIMBots","autodiscovered")+")") 642 | if self.running: 643 | services = get_services(provider['listUrl']) 644 | if services: 645 | for service in services: 646 | # services descriptions might contain a more accurate server name 647 | if ("provider" in service) and service['provider'] and (service['provider'] != top.text(0)): 648 | top.setText(0,service['provider']) 649 | child = QtGui.QTreeWidgetItem(top) 650 | child.setText(0,service['name']) 651 | # store the whole service dict 652 | child.setData(0,QtCore.Qt.UserRole,json.dumps(service)) 653 | # construct tooltip with different pieces of data 654 | if "description" in service: 655 | child.setToolTip(0,service['description']) 656 | if "inputs" in service: 657 | child.setToolTip(0,child.toolTip(0)+"\n"+"inputs: "+",".join(service['inputs'])) 658 | if "outputs" in service: 659 | child.setToolTip(0,child.toolTip(0)+"\n"+"outputs: "+",".join(service['outputs'])) 660 | authenticated = get_service_config(provider['listUrl'],service['id']) 661 | if authenticated: 662 | child.setIcon(0,QtGui.QIcon(":/icons/button_valid.svg")) # FreeCAD builtin icon 663 | child.setToolTip(0,child.toolTip(0)+"\n"+translate("BIMBots","Authenticated")) 664 | top.setExpanded(True) 665 | else: 666 | if self.form.checkShowUnreachable.isChecked(): 667 | # show provider as disabled: remove Enabled from flags 668 | # This doesn't work well as it becomes unselectable and therefore not removable 669 | # top.setFlags(top.flags() & ~QtCore.Qt.ItemIsEnabled) 670 | # instead, paint it with the disabled color and show a daunting icon 671 | top.setIcon(0,QtGui.QIcon(":/icons/button_invalid.svg")) 672 | palette = QtGui.QApplication.palette() 673 | top.setForeground(0,palette.brush(palette.Disabled,palette.Text)) 674 | top.setToolTip(0,top.toolTip(0)+" - "+translate("BIMBots","Unreachable")) 675 | else: 676 | # remove it from the list 677 | self.form.servicesList.takeTopLevelItem(self.form.servicesList.topLevelItemCount()-1) 678 | self.form.progressBar.setValue(int(100*(n/len(providers)))) 679 | n += 1 680 | 681 | # clean the progress bar 682 | self.running = False 683 | self.form.groupProgress.hide() 684 | self.form.groupRescan.hide() 685 | 686 | 687 | def on_list_click(self,arg1=None,arg2=None): 688 | 689 | "Checks which items are selected and what options should be enabled. Args not used. Returns nothing" 690 | 691 | # start by disabling everything 692 | self.form.buttonRemoveService.setEnabled(False) 693 | self.form.buttonAuthenticate.setEnabled(False) 694 | self.form.buttonRun.setEnabled(False) 695 | serviceitem = self.form.servicesList.currentItem() 696 | scopeitem = self.form.scopeList.currentItem() 697 | if scopeitem: 698 | if scopeitem.text() == translate("BIMBots","Test output only"): 699 | # this is a test item that doesn't send data toany service 700 | self.form.buttonRun.setEnabled(True) 701 | if serviceitem: 702 | if serviceitem.parent(): 703 | # this is a service 704 | self.form.buttonAuthenticate.setEnabled(True) 705 | if translate("BIMBots","Authenticated") in serviceitem.toolTip(0): 706 | if scopeitem: 707 | self.form.buttonRun.setEnabled(True) 708 | else: 709 | # this is a provider 710 | if 'custom' in json.loads(serviceitem.data(0,QtCore.Qt.UserRole)): 711 | self.form.buttonRemoveService.setEnabled(True) 712 | 713 | def on_click_help(self,arg=None): 714 | 715 | "Opens a browser to show the BIMbots help page. Arg not used. Returns nothing" 716 | 717 | url = "https://github.com/opensourceBIM/BIMbots-FreeCAD/blob/master/doc/ui-documentation.md" 718 | err = "Error: Unable to launch web browser. Please paste the following URL in your web browser: " + url 719 | try: 720 | import webbrowser 721 | except: 722 | print(err) 723 | else: 724 | res = webbrowser.open(url) 725 | if not res: 726 | print(err) 727 | 728 | def validate_fields(self,arg=None): 729 | 730 | "Validates the editable fields, turn buttons on/off as needed. Arg not used. Returns nothing" 731 | 732 | # disable everything first 733 | self.form.buttonSaveService.setEnabled(False) 734 | self.form.buttonSaveAuthenticate.setEnabled(False) 735 | 736 | if self.form.lineEditServiceName.text() and self.form.lineEditServiceUrl.text(): 737 | self.form.buttonSaveService.setEnabled(True) 738 | 739 | if self.form.lineEditAuthenticateUrl.text() and self.form.lineEditAuthenticateToken.text(): 740 | self.form.buttonSaveAuthenticate.setEnabled(True) 741 | 742 | def on_add_service(self): 743 | 744 | "Adds a custom service provider and its services and updates the Available Services list. Returns nothing" 745 | 746 | name = self.form.lineEditServiceName.text() 747 | url = self.form.lineEditServiceUrl.text() 748 | if name and url: 749 | save_custom_provider(name,url) 750 | self.form.groupAddService.hide() 751 | QtCore.QTimer.singleShot(0,self.on_scan) 752 | 753 | def on_remove_service(self): 754 | 755 | "Removes a custom service provider from the config. Returns nothing" 756 | 757 | serviceitem = self.form.servicesList.currentItem() 758 | if serviceitem: 759 | if not serviceitem.parent(): 760 | # this is a provider 761 | name = serviceitem.text(0) 762 | data = json.loads(serviceitem.data(0,QtCore.Qt.UserRole)) 763 | if 'custom' in data: 764 | reply = QtGui.QMessageBox.question(None, 765 | translate("BIMBots","Removal warning"), 766 | translate("BIMBots","Remove provider")+" \""+name+"\"? "+translate("BIMBots","This cannot be undone."), 767 | QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, 768 | QtGui.QMessageBox.No) 769 | if reply == QtGui.QMessageBox.Yes: 770 | delete_custom_provider(data['listUrl']) 771 | QtCore.QTimer.singleShot(0,self.on_scan) 772 | 773 | def on_authenticate(self): 774 | 775 | "Opens a browser window to authenticate. Returns nothing" 776 | 777 | self.form.groupAuthenticate.show() 778 | self.form.lineEditAuthenticateToken.clear() 779 | self.form.lineEditAuthenticateUrl.clear() 780 | serviceitem = self.form.servicesList.currentItem() 781 | if serviceitem: 782 | if serviceitem.parent(): 783 | # this is a service 784 | service_data = json.loads(serviceitem.data(0,QtCore.Qt.UserRole)) 785 | if "oauth" in service_data: 786 | if "registerUrl" in service_data['oauth']: 787 | step1 = authenticate_step_1(service_data['oauth']['registerUrl']) 788 | if step1: 789 | auth_url = service_data['oauth']['authorizationUrl'] 790 | client_id = step1['client_id'] 791 | service_name = service_data['name'] 792 | step2 = authenticate_step_2(auth_url,client_id,service_name) 793 | if step2 == True: 794 | return 795 | msg = translate("BIMBots","Unable to open a web browser. Please paste the following URL in your web browser")+": " + step2 796 | QtGui.QMessageBox.information(None,"Error",msg) 797 | print("Error: Unable to start authentication!") 798 | 799 | def on_save_authenticate(self): 800 | 801 | "Authenticates with the selected service and updates the Available Services list. Returns nothing" 802 | 803 | service_url = self.form.lineEditAuthenticateUrl.text() 804 | token = self.form.lineEditAuthenticateToken.text() 805 | if service_url and token: 806 | serviceitem = self.form.servicesList.currentItem() 807 | if serviceitem: 808 | if serviceitem.parent(): 809 | # this is a service 810 | provider_data = json.loads(serviceitem.parent().data(0,QtCore.Qt.UserRole)) 811 | provider_url = provider_data['listUrl'] 812 | service_data = json.loads(serviceitem.data(0,QtCore.Qt.UserRole)) 813 | service_id = service_data['id'] 814 | service_name = service_data['name'] 815 | save_authentication(provider_url,service_id,service_name,service_url,token) 816 | self.form.groupAuthenticate.hide() 817 | QtCore.QTimer.singleShot(0,self.on_scan) 818 | return 819 | print("Error: Unable to register authentication!") 820 | 821 | def on_run(self): 822 | 823 | "Runs the selected service. Returns nothing" 824 | 825 | results = None 826 | serviceitem = self.form.servicesList.currentItem() 827 | scopeitem = self.form.scopeList.currentItem() 828 | service_data = None 829 | if scopeitem: 830 | if scopeitem.text() == "Test output only": 831 | # Test item - don't run any service, just show dummy results from file 832 | payload_response = os.path.join(os.path.dirname(__file__),"testfiles","test payload response.json") 833 | if os.path.exists(payload_response): 834 | with open(payload_response) as json_file: 835 | results = json.load(json_file) 836 | elif serviceitem: 837 | if serviceitem.parent(): 838 | # this is a service 839 | if translate("BIMBots","Authenticated") in serviceitem.toolTip(0): 840 | if scopeitem: 841 | # we have scope and authenticated service: let's run! 842 | self.running = True 843 | # setup the progress bar 844 | self.form.groupProgress.show() 845 | self.form.progressBar.setFormat(translate("BIMBots","Preparing")) 846 | self.form.progressBar.setValue(25) 847 | provider_data = json.loads(serviceitem.parent().data(0,QtCore.Qt.UserRole)) 848 | provider_url = provider_data['listUrl'] 849 | service_data = json.loads(serviceitem.data(0,QtCore.Qt.UserRole)) 850 | service_id = service_data['id'] 851 | if scopeitem.text() == translate("BIMBots","Test payload"): 852 | # no need to check for current document if running a test payload 853 | self.form.progressBar.setFormat(translate("BIMBots","Sending data")) 854 | self.form.progressBar.setValue(75) 855 | results = send_test_payload(provider_url,service_id) 856 | elif scopeitem.text() == translate("BIMBots","Choose IFC file"): 857 | ret = QtGui.QFileDialog.getOpenFileName(None, translate("BIMBots","Choose an existing IFC file"), None, translate("BIMBots","IFC files (*.ifc)")) 858 | if ret: 859 | file_path = ret[0] 860 | if file_path: 861 | self.form.progressBar.setFormat(translate("BIMBots","Sending data")) 862 | self.form.progressBar.setValue(75) 863 | results = send_ifc_payload(provider_url,service_id,file_path) 864 | else: 865 | if FreeCAD.ActiveDocument: 866 | objectslist = [] 867 | self.form.progressBar.setFormat(translate("BIMBots","Saving IFC file")) 868 | self.form.progressBar.setValue(25) 869 | if scopeitem.text() == translate("BIMBots","Selected objects"): 870 | objectslist = FreeCADGui.Selection.getSelection() 871 | elif scopeitem.text() == translate("BIMBots","All visible objects"): 872 | objectslist = [o for o in FreeCAD.ActiveDocument.Objects if o.ViewObject and hasattr(o.ViewObject,"Visibility") and o.ViewObject.Visibility] 873 | else: 874 | objectslist = FreeCAD.ActiveDocument.Objects 875 | if objectslist: 876 | file_path = self.save_ifc(objectslist) 877 | if file_path: 878 | self.form.progressBar.setFormat(translate("BIMBots","Sending data")) 879 | self.form.progressBar.setValue(75) 880 | results = send_ifc_payload(provider_url,service_id,file_path) 881 | 882 | # show the results 883 | self.form.groupProgress.hide() 884 | if results: 885 | self.form.groupServices.hide() 886 | self.form.groupRun.hide() 887 | self.form.groupResults.show() 888 | if isinstance(results,dict): 889 | # json results 890 | self.form.textResults.hide() 891 | self.form.treeResults.show() 892 | self.form.treeResults.clear() 893 | self.fill_item(self.form.treeResults.invisibleRootItem(), results) 894 | else: 895 | # text results 896 | if service_data: 897 | # detect if this is a BCF file - we just analyze the first output type for now 898 | if ("outputs" in service_data) and service_data["outputs"] and ("BCF" in service_data["outputs"][0]): 899 | # BCF results 900 | zipfile = tempfile.mkstemp(suffix=".bcf.zip")[1] 901 | f = open(zipfile,"wb") 902 | f.write(results) 903 | f.close() 904 | results = translate("BIMBots","BCF results saved as:")+" " + zipfile + ". "+translate("BIMBots","BCF viewing is not yet implemented.") 905 | if DEBUG: 906 | print(translate("BIMBots","Results")+":",results) 907 | self.form.textResults.show() 908 | self.form.treeResults.hide() 909 | self.form.textResults.clear() 910 | self.form.textResults.setPlainText(results) 911 | else: 912 | FreeCAD.Console.PrintError(translate("BIMBots","Error: No results obtained")+"\n") 913 | QtGui.QMessageBox.critical(None,translate("BIMBots","Empty response"), 914 | translate("BIMBots","The server didn't send a valid response. There can be many reasons to this, but the most likely is that the IFC file generated from your model wasn't accepted by the server. Try working with only a couple of selected objects first, to see if the service is responding correctly.")) 915 | 916 | def fill_item(self, item, value, link=False): 917 | 918 | "fills a QtreeWidget or QtreeWidgetItem with a dict, list or text/number value. If link is true, paints in link color. Returns nothing" 919 | 920 | # adapted from https://stackoverflow.com/questions/21805047/qtreewidget-to-mirror-python-dictionary 921 | item.setExpanded(True) 922 | if isinstance(value,dict): 923 | for key, val in sorted(value.items()): 924 | child = QtGui.QTreeWidgetItem() 925 | item.addChild(child) 926 | child.setExpanded(True) 927 | if tostr(key).lower().startswith("ifc"): 928 | palette = QtGui.QApplication.palette() 929 | child.setForeground(0,palette.brush(palette.Active,palette.Link)) 930 | child.setToolTip(0,"typeLink:"+tostr(key)) 931 | if DECAMELIZE: 932 | key = ''.join(map(lambda x: x if x.islower() else " "+x, key)).strip() 933 | child.setText(0, tostr(key)) 934 | childlink = link 935 | if (tostr(key).lower() == "guid"): 936 | childlink = "uuidLink:" 937 | elif ((tostr(key).lower() == "name") and (tostr(val) != tostr(val).upper())): 938 | childlink = "nameLink:" 939 | elif ((tostr(key).lower() == "type") and (tostr(val).lower().startswith("ifc"))): 940 | childlink = "typeLink:" 941 | self.fill_item(child, val, childlink) 942 | elif isinstance(value,list): 943 | for index,val in enumerate(value): 944 | child = QtGui.QTreeWidgetItem() 945 | item.addChild(child) 946 | child.setExpanded(True) 947 | child.setText(0, tostr(index)) 948 | self.fill_item(child, val) 949 | else: 950 | item.setText(1, tostr(value)) 951 | if link: 952 | palette = QtGui.QApplication.palette() 953 | item.setForeground(1,palette.brush(palette.Active,palette.Link)) 954 | item.setToolTip(1,str(link)+tostr(value)) 955 | 956 | def on_click_results(self,item,column): 957 | 958 | "Selects associated objects in document when an item is clicked. Returns nothing" 959 | 960 | tosel = [] 961 | tooltip = item.toolTip(column) 962 | if FreeCAD.ActiveDocument: 963 | if tooltip: 964 | if "Link:" in tooltip: 965 | tooltip = tooltip.split("Link:") 966 | if tooltip[0] == "uuid": 967 | for obj in FreeCAD.ActiveDocument.Objects: 968 | if hasattr(obj,"IfcData"): # FreeCAD 0.19 969 | if "IfcUID" in obj.IfcData.keys(): 970 | if str(obj.IfcData["IfcUID"]) == tooltip[1]: 971 | tosel.append(obj) 972 | elif hasattr(obj,"IfcAttributes"): # FreeCAD 0.18 973 | if "IfcUID" in obj.IfcAttributes.keys(): 974 | if str(obj.IfcAttributes["IfcUID"]) == tooltip[1]: 975 | tosel.append(obj) 976 | elif tooltip[0] == "name": 977 | for obj in FreeCAD.ActiveDocument.Objects: 978 | if obj.Label == tooltip[1]: 979 | tosel.append(obj) 980 | elif tooltip[0] == "type": 981 | if tooltip[1].lower().startswith("ifc"): 982 | ifctype = tooltip[1][3:] 983 | for obj in FreeCAD.ActiveDocument.Objects: 984 | if hasattr(obj,"IfcType"): # FreeCAD 0.19 985 | if obj.IfcType.lower().replace(" ","") == ifctype.lower(): 986 | tosel.append(obj) 987 | elif hasattr(obj,"IfcRole"): # FreeCAD 0.18 988 | if obj.IfcRole.lower().replace(" ","") == ifctype.lower(): 989 | tosel.append(obj) 990 | if tosel: 991 | FreeCADGui.Selection.clearSelection() 992 | for obj in tosel: 993 | FreeCADGui.Selection.addSelection(obj) 994 | 995 | def save_ifc(self,objectslist): 996 | 997 | "Saves an IFC file with the given objects to a temporary location. Returns the file path." 998 | 999 | tf = tempfile.mkstemp(suffix=".ifc")[1] 1000 | print("Saving temporary IFC file at",tf) 1001 | import importIFC 1002 | importIFC.export(objectslist,tf) 1003 | return tf 1004 | 1005 | def on_cancel(self): 1006 | 1007 | "Cancels the current operation by setting the running flag to False. Returns nothing" 1008 | 1009 | self.running = False 1010 | 1011 | def get_defaults(self): 1012 | 1013 | "Sets the state of different widgets from previously saved state. Return nothing" 1014 | 1015 | settings = FreeCAD.ParamGet("User parameter:Plugins/BIMbots") 1016 | self.form.checkAutoDiscover.setChecked(settings.GetBool("checkAutoDiscover",True)) 1017 | self.form.checkShowUnreachable.setChecked(settings.GetBool("checkShowUnreachable",True)) 1018 | 1019 | def save_defaults(self,arg=None): 1020 | 1021 | "Save the state of different widgets. Arg not used. Returns nothing" 1022 | 1023 | settings = FreeCAD.ParamGet("User parameter:Plugins/BIMbots") 1024 | settings.SetBool("checkAutoDiscover",self.form.checkAutoDiscover.isChecked()) 1025 | settings.SetBool("checkShowUnreachable",self.form.checkShowUnreachable.isChecked()) 1026 | 1027 | 1028 | 1029 | ############# If running from the command line, print a list of available services 1030 | 1031 | 1032 | if __name__ == "__main__": 1033 | print_services() 1034 | -------------------------------------------------------------------------------- /bimbots.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Form 4 | 5 | 6 | 7 | 0 8 | 0 9 | 276 10 | 1339 11 | 12 | 13 | 14 | BIMbots 15 | 16 | 17 | 18 | 19 | 20 | 21 | 0 22 | 0 23 | 24 | 25 | 26 | 27 | 16777215 28 | 32 29 | 30 | 31 | 32 | 33 | 16 34 | 35 | 36 | 37 | BIMbots logo 38 | 39 | 40 | Qt::AlignCenter 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | false 51 | 52 | 53 | 54 | 55 | 56 | 10 57 | 58 | 59 | connecting... 60 | 61 | 62 | 63 | 64 | 65 | 66 | Cancel the current connection 67 | 68 | 69 | Cancel 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Available services 85 | 86 | 87 | 88 | 89 | 90 | 91 | 0 92 | 0 93 | 94 | 95 | 96 | false 97 | 98 | 99 | 100 | 1 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | Tries to obtain a list of available services from all registered servers 109 | 110 | 111 | Rescan services 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | Service discovery options: 124 | 125 | 126 | 127 | 128 | 129 | Auto-discover new services 130 | 131 | 132 | true 133 | 134 | 135 | 136 | 137 | 138 | 139 | Show unreachable providers 140 | 141 | 142 | true 143 | 144 | 145 | 146 | 147 | 148 | 149 | false 150 | 151 | 152 | 153 | Filter by category... 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | false 164 | 165 | 166 | max price: 167 | 168 | 169 | 170 | 171 | 172 | 173 | false 174 | 175 | 176 | 0 USD 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | Scan 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | Cancel 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | Adds a new entry to the list of known service providers 217 | 218 | 219 | Add custom service provider 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | Add custom service provider: 232 | 233 | 234 | false 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | Name: 243 | 244 | 245 | 246 | 247 | 248 | 249 | URL: 250 | 251 | 252 | 253 | 254 | 255 | 256 | A custom name to identify this services provider 257 | 258 | 259 | 260 | 261 | 262 | 263 | The URL of this services provider 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | false 275 | 276 | 277 | Add this server to the list of known service providers 278 | 279 | 280 | Add 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | Cancels the operation 293 | 294 | 295 | Cancel 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | false 313 | 314 | 315 | Removes a selected service from the list above 316 | 317 | 318 | Remove custom service provider 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | false 331 | 332 | 333 | <html><head/><body><p>Authenticates with a selected service. Opens a web browser window.</p><p><br/></p><p>If unable to obtain a token, you might need to add internal services to your account under &quot;User Settings&quot; in the BIMServer panel </p></body></html> 334 | 335 | 336 | Authenticate 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | Paste authentication credentials here: 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | Service URL: 357 | 358 | 359 | 360 | 361 | 362 | 363 | The service URL returned by the authentication process 364 | 365 | 366 | 367 | 368 | 369 | 370 | Token: 371 | 372 | 373 | 374 | 375 | 376 | 377 | The token returned by the authentication process 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | false 389 | 390 | 391 | Saves the authentication credentials 392 | 393 | 394 | Save 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | Cancel 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | Run selected service 427 | 428 | 429 | 430 | 431 | 432 | Run service on: 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 0 441 | 0 442 | 443 | 444 | 445 | 446 | 0 447 | 0 448 | 449 | 450 | 451 | 452 | 16777215 453 | 80 454 | 455 | 456 | 457 | 458 | All document objects 459 | 460 | 461 | <html><head/><body><p>Sends all the objects contained in the active document to the selected service. Warning, all the inner object components wil be sent too, this is usually not what you want. Use for testing purposes only.</p></body></html> 462 | 463 | 464 | 465 | 466 | All visible objects 467 | 468 | 469 | <html><head/><body><p>Sends all the visible objects to the selected service. All the needed components will be gathered automatically. Choose this option if your model doesn't use IFC building structures such as Sites, Buildings or Storeys</p></body></html> 470 | 471 | 472 | 473 | 474 | Selected objects 475 | 476 | 477 | <html><head/><body><p>Sends the selected objects to the given service. All the necessary components will be gathered authomatically. You can select only one or more groups or sites, buildings or storeys, all their child objects will be collected too. This is usually the best option. </p></body></html> 478 | 479 | 480 | 481 | 482 | Choose IFC file 483 | 484 | 485 | <html><head/><body><p>Allows you to select an existing IFC file to send</p></body></html> 486 | 487 | 488 | 489 | 490 | Test payload 491 | 492 | 493 | <html><head/><body><p>Sends a very simple test payload IFC to the service, that is guaranteed to work. Use this to test if the service works correctly.</p></body></html> 494 | 495 | 496 | 497 | 498 | Test output only 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | false 507 | 508 | 509 | <html><head/><body><p>Runs the selected service and displays the results</p><p><br/></p><p>If unable to obtain a response, you might need to add deserializers to your account under &quot;User Settings&quot; in the BIMServer panel </p></body></html> 510 | 511 | 512 | Run service 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | Results 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 12 537 | 538 | 539 | 2 540 | 541 | 542 | true 543 | 544 | 545 | 100 546 | 547 | 548 | 100 549 | 550 | 551 | true 552 | 553 | 554 | 555 | key 556 | 557 | 558 | 559 | 560 | value 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | Closes the results screen and re-shows the service options 569 | 570 | 571 | Run other service 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | <html><head/><body><p><a href="https://github.com/opensourceBIM/BIMbots-FreeCAD/blob/master/doc/ui-documentation.md"><span>BIMbots plugin documentation</span></a></p></body></html> 587 | 588 | 589 | Qt::AlignCenter 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | -------------------------------------------------------------------------------- /doc/documentation.md: -------------------------------------------------------------------------------- 1 | Module bimbots 2 | ============== 3 | This module provides tools to communicate with BIMbots services, and 4 | a FreeCAD GUI, that autoruns if this module is executed as a FreeCAD macro or 5 | simply imported from the FreeCAD Python console. If run from the command line, 6 | it prints a summary of available (and reachable) BIMbots services. 7 | 8 | See also the [GUI documentation](ui-documentation.md) for GUI usage inside FreeCAD 9 | 10 | Functions 11 | --------- 12 | 13 | `authenticate_step_1(register_url)` 14 | : Sends an authentication request to the given server. Returns the result json as a dict 15 | 16 | `authenticate_step_2(authorization_url, client_id, service_name)` 17 | : Opens the authorization url in an external browser. Returns True if successful, the authorization URL to be shown if not. 18 | 19 | `beautyprint(data)` 20 | : Beautifies (prints with indents) and prints a given dictionary or json string 21 | 22 | `delete_custom_provider(list_url)` 23 | : Removes a custom provider from the config file. Returns nothing. 24 | 25 | `get_config_value(value)` 26 | : Returns the given config value from the config file, or defaults to the default value if existing 27 | 28 | `get_custom_providers()` 29 | : Returns custom providers from the config file 30 | 31 | `get_plugin_info()` 32 | : Returns a dict with info about this plugin, usable for ex. by FreeCAD 33 | 34 | `get_service_config(provider_url, service_id)` 35 | : Returns the service associated with the given provider url and service id if it has already been authenticated 36 | 37 | `get_service_providers(autodiscover=True, url=None)` 38 | : Returns a list of dicts {name,desciption,listUrl} of BIMbots services obtained from the stored config and, 39 | if autodiscover is True, from the given service list url (or from the default one if none is given). 40 | 41 | `get_services(list_url)` 42 | : Returns a list of dicts of service plugins available from a given service provider list url 43 | 44 | `launch_ui()` 45 | : Opens the BIMbots task panel in FreeCAD 46 | 47 | `print_services()` 48 | : Prints a list of available (and reachable) services 49 | 50 | `read_config()` 51 | : Reads the config file, if found, and returns a dict of its contents 52 | 53 | `save_authentication(provider_url, service_id, service_name, service_url, token)` 54 | : Saved the given service authentication data to the config file. Returns nothing. 55 | 56 | `save_config(config)` 57 | : Saves the given dict to the config file. Overwrites everything, be careful! Returns nothing. 58 | 59 | `save_custom_provider(name, list_url)` 60 | : Saves a custom services provider to the config file. Returns nothing. 61 | 62 | `save_default_config()` 63 | : Saves the default settings to the config file. Returns nothing 64 | 65 | `send_ifc_payload(provider_url, service_id, file_path)` 66 | : Sends a given IFC file to the given service. Returns the json response as a dict 67 | 68 | `send_test_payload(provider_url, service_id)` 69 | : Sends a test IFC file to the given service. Returns the json response as a dict 70 | 71 | `tostr(something)` 72 | : a convenience function to unify py2/py3 conversion to string 73 | 74 | Classes 75 | ------- 76 | 77 | `bimbots_panel` 78 | : This is the interface panel implementation of bimbots.ui. It is meant to run inside FreeCAD. 79 | It is launched by the launch_ui() function 80 | 81 | ### Methods 82 | 83 | `__init__(self)` 84 | : Initialize self. See help(type(self)) for accurate signature. 85 | 86 | `fill_item(self, item, value, link=False)` 87 | : fills a QtreeWidget or QtreeWidgetItem with a dict, list or text/number value. If link is true, paints in link color. Returns nothing 88 | 89 | `getStandardButtons(self)` 90 | : The list of buttons to show above the task panel. Returns a Close button only. 91 | 92 | `get_defaults(self)` 93 | : Sets the state of different widgets from previously saved state. Return nothing 94 | 95 | `needsFullSpace(self)` 96 | : Notifies FreeCAD that this panel needs max height. Returns True 97 | 98 | `on_add_service(self)` 99 | : Adds a custom service provider and its services and updates the Available Services list. Returns nothing 100 | 101 | `on_authenticate(self)` 102 | : Opens a browser window to authenticate. Returns nothing 103 | 104 | `on_cancel(self)` 105 | : Cancels the current operation by setting the running flag to False. Returns nothing 106 | 107 | `on_click_help(self, arg=None)` 108 | : Opens a browser to show the BIMbots help page. Arg not used. Returns nothing 109 | 110 | `on_click_results(self, item, column)` 111 | : Selects associated objects in document when an item is clicked. Returns nothing 112 | 113 | `on_list_click(self, arg1=None, arg2=None)` 114 | : Checks which items are selected and what options should be enabled. Args not used. Returns nothing 115 | 116 | `on_remove_service(self)` 117 | : Removes a custom service provider from the config. Returns nothing 118 | 119 | `on_run(self)` 120 | : Runs the selected service. Returns nothing 121 | 122 | `on_save_authenticate(self)` 123 | : Authenticates with the selected service and updates the Available Services list. Returns nothing 124 | 125 | `on_scan(self)` 126 | : Scans for providers and services and updates the Available Services list. Returns nothing 127 | 128 | `reject(self)` 129 | : Called when the "Close" button of the task dialog is pressed, closes the panel. Returns nothing 130 | 131 | `save_defaults(self, arg=None)` 132 | : Save the state of different widgets. Arg not used. Returns nothing 133 | 134 | `save_ifc(self, objectslist)` 135 | : Saves an IFC file with the given objects to a temporary location. Returns the file path. 136 | 137 | `validate_fields(self, arg=None)` 138 | : Validates the editable fields, turn buttons on/off as needed. Arg not used. Returns nothing 139 | -------------------------------------------------------------------------------- /doc/images/bimbots-ui-01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-01.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-02.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-03.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-04.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-05.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-06.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-07.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-08.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-09.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-09.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-10.jpg -------------------------------------------------------------------------------- /doc/images/bimbots-ui-11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/doc/images/bimbots-ui-11.jpg -------------------------------------------------------------------------------- /doc/implementation-notes.md: -------------------------------------------------------------------------------- 1 | # BIMBots plugin 2 | 3 | Basically: Don't use the BIMserver as a whole: Use only selected services from a BIMserver. 4 | 5 | ### Links 6 | 7 | * [BIMbots wiki](https://github.com/opensourceBIM/BIM-Bot-services/wiki) 8 | * [BIMbots auth doc](https://github.com/opensourceBIM/BIM-Bot-services/wiki/Building-a-client-application#32-navigate-to-authorization-url) 9 | * [FreeCAD BIMServer plugin](https://github.com/yorikvanhavre/WebTools/blob/master/BIMServer.py) 10 | * [Revit plugin example](https://www.youtube.com/watch?v=CX2F21NFI3A) 11 | * [Reading and writing json to/from a file](https://stackabuse.com/reading-and-writing-json-to-a-file-in-python/) 12 | 13 | ### General structure 14 | 15 | * Service provided by BIMServer plugins, or -maybe- any other kind of service providers 16 | * List of services online. [Sample](https://github.com/opensourceBIM/BIMserver-Repository/blob/master/serviceproviders.json): 17 | 18 | ``` 19 | { 20 | "active": [ 21 | { 22 | "name": "Default JAR runner", 23 | "description": "Default JAR runner", 24 | "listUrl": "http://localhost:8082/servicelist" 25 | }, { 26 | "name": "Experimentalserver.com", 27 | "description": "Experimental BIMserver", 28 | "listUrl": "https://thisisanexperimentalserver.com/servicelist" 29 | } 30 | ] 31 | } 32 | ``` 33 | 34 | * Each service's listurl returns a list of plugins. This one from [local bimserver](http://localhost:8082/servicelist): 35 | 36 | ``` 37 | { 38 | "capabilities": 39 | [ 40 | "WEBSOCKET" 41 | ], 42 | "services": 43 | [ 44 | { 45 | "id":2097206, 46 | "name":"IFC Analytics Service", 47 | "description":"IFC Analytics Service", 48 | "provider":"Yorik's test BIMserver", 49 | "providerIcon":"/img/bimserver.png", 50 | "inputs": 51 | [ 52 | "IFC_STEP_2X3TC1" 53 | ], 54 | "outputs": 55 | [ 56 | "IFC_ANALYTICS_JSON_1_0" 57 | ], 58 | "oauth": 59 | { 60 | "authorizationUrl":"http://localhost:8082/oauth/authorize", 61 | "registerUrl":"http://localhost:8082/oauth/register", 62 | "tokenUrl":"http://localhost:8082/oauth/access" 63 | }, 64 | "resourceUrl":"http://localhost:8082/services" 65 | }, 66 | { 67 | "id":2162742, 68 | "name":"Simple Analyses Service", 69 | "description":"BIMserver plugin that provides an analysis of a model and and outputs it into json", 70 | "provider":"Yorik's test BIMserver", 71 | "providerIcon":"/img/bimserver.png", 72 | "inputs": 73 | [ 74 | "IFC_STEP_2X3TC1" 75 | ], 76 | "outputs": 77 | [ 78 | "UNSTRUCTURED_UTF8_TEXT_1_0" 79 | ], 80 | "oauth": 81 | { 82 | "authorizationUrl":"http://localhost:8082/oauth/authorize", 83 | "registerUrl":"http://localhost:8082/oauth/register", 84 | "tokenUrl":"http://localhost:8082/oauth/access" 85 | }, 86 | "resourceUrl":"http://localhost:8082/services" 87 | }, 88 | { 89 | "id":2228278, 90 | "name":"Detailed Analyses Service", 91 | "description":"BIMserver plugin that provides a detailed analysis of a model and outputs it into json", 92 | "provider":"Yorik's test BIMserver", 93 | "providerIcon":"/img/bimserver.png", 94 | "inputs": 95 | [ 96 | "IFC_STEP_2X3TC1" 97 | ], 98 | "outputs": 99 | [ 100 | "UNSTRUCTURED_UTF8_TEXT_1_0" 101 | ], 102 | "oauth": 103 | { 104 | "authorizationUrl":"http://localhost:8082/oauth/authorize", 105 | "registerUrl":"http://localhost:8082/oauth/register", 106 | "tokenUrl":"http://localhost:8082/oauth/access" 107 | }, 108 | "resourceUrl":"http://localhost:8082/services" 109 | } 110 | ] 111 | } 112 | ``` 113 | 114 | * [List of possible input/output types](https://github.com/opensourceBIM/BIM-Bot-services/wiki/Schemas) 115 | * Registration process: The authentication is by user, using oauth2 116 | * User might need to re-authenticate, for ex. if the service went down 117 | * Send request to registerUrl: 118 | 119 | ``` 120 | { 121 | "redirect_url": "redirect url", 122 | "client_name": "name of this client", 123 | "client_description": "test", 124 | "client_icon": "url to icon", 125 | "client_url": "url to client website", 126 | "type": "pull" 127 | } 128 | ``` 129 | 130 | * Response: 131 | 132 | ``` 133 | { 134 | "client_id": "client id", 135 | "client_secret": "clientSecret", 136 | "issued_at": "issued at", 137 | "expires_in": "expires in" 138 | } 139 | ``` 140 | 141 | Not sure what the response values are for yet, apparently they are not needed to use bimbots services. 142 | 143 | * When using BIMserver as a service provider (B), you can also do this step manually by using BIMvie.ws. Go to Server | OAuth Server and click "Manually register server" 144 | * After receiving this response, user needs to go online to authorize. Open a web browser at the authorizationUrl - HTTP GET with arguments: 145 | * redirect_uri: URI of the page the user will be redirected to after successful/failed authorization. You can use the special value "SHOW_CODE" if your application is not webbased and you want the user to copy&paste the code to your application. WARNING - HERE IT'S URI, NOT URL 146 | * response_type: "code" 147 | * client_id: "client_id" 148 | * auth_type: "service" // Because we want to run a service, other types exist 149 | * state: Stringified JSON in which additional state can be encoded. For running services this is a simple json object with one field called "_serviceName", the value should be the "name" found in servicr list above 150 | * The response comes by calling the redirect url and giving it code, address, soid, serviceaddress 151 | * Accessing the bimbot by HTTP POST, at serviceaddress + soid. Ex: http://localhost:8082/services/3014734 152 | * HTTP request headers: 153 | * Input-Type: IFC_STEP_2X3TC1 154 | * Context-Id: A Context-Id, you can only provide this when this is the second time running this service. By sending it you indicate that this model you are sending belongs to the same context (project for example). Some servers might be able to do something with it (e.a. store all models together in the same project). 155 | * Token: the token, this is the "code" from step 3.3 156 | * Accept-Flow: A list of flow-types this client accepts. The preferred flow should come first. Depending on the capabilities of B and the information sent as Accept-Flow a specific flow will be used. Current types: "SYNC": "ASYNC_WS" (ASYNC_WS, SYNC) 157 | * HTTP request body: the IFC file 158 | * HTTP response headers: 159 | * Output-Type: UNSTRUCTURED_UTF8_TEXT_1_0 160 | * Data-Title: The title of the output, given by the service. Ex: BimBotDemoService Results 161 | * Content-Type: text/plain;charset=utf-8 162 | * Content-Length: 72 163 | * Context-Id: Some servers create a context when a service is being run, in those cases a Context-Id is returned. A client can decide to store this Context-Id and send it as an HTTP whenever the service is being run on the same project for example. 164 | * HTTP response body (example): 165 | * Output Data: 166 | * Number of objects: 1759 167 | * Number of products: 54 168 | * Number of triangles: 912 169 | 170 | ### Questions 171 | 172 | * Paid services too? (The Revit interface has price range controls). R - There can be BIM Bots online that are free to use; but there can also be BIM Bots that require a subscription of some kind. 173 | * Filter services? By which critetia? R - That could be handy in the long run when there are thousands of BIM Bots available. At this moment not a high priority. 174 | * Any more service I could test with? (IFC validating, etc) 175 | * Autorization only by redirect url? What about unreachable apps? (Firewall, etc) What about tokens? - Need to investigate 176 | * BIMBots logo? R - [logo1](http://bimbots.org/wp-content/uploads/sites/4/2017/08/BIM-Bots-viewer.png),[logo2](http://bimbots.org/wp-content/uploads/sites/4/2017/08/BIM-Bots-validationchecker.png) 177 | 178 | ### Interface: 179 | 180 | * Preferences: Main services list url 181 | * List of service providers + services (cached? if yes, reload button) with authenticated status 182 | * Add services manually (individual services? services list?) 183 | * Remove services 184 | * Authenticate button (no need to enter user details) 185 | * IFC scope options (root objects, all visible, all) 186 | * Results screen: 187 | * Text 188 | * Json? 189 | * BCF? 190 | * IFC? 191 | * If no token system available, need a php script somewhere that shows the code/soid to the user to be copied/pasted in the UI 192 | * SOID + token(code) fields 193 | * Sync/aSync? If async, needs a kind of "running" status, + callback system 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /doc/ui-documentation.md: -------------------------------------------------------------------------------- 1 | ## BIMBots FreeCAD plugin documentation 2 | 3 | 4 | 5 | ![header image](images/bimbots-ui-01.jpg) 6 | 7 | 8 | 9 | The BIMBots plugin is an addon/macro for FreeCAD to to allow it to connect and use [BIMBots](http://bimbots.org/) services. BIMbots is an open-source system built on top of the [BIMServer](http://bimserver.org) project. In a nutshell, it allows an application to connect to a BIMServer instance, and use some of the services hosted on it on the fly, without the need to setup and upload a proper project to the server. 10 | 11 | The BIMBots plugin can also be run from the command line, or imported as a python module(check the [API documentation](documentation.md)). 12 | 13 | ### Installing 14 | 15 | **Note:** below method not yet available - At the moment, you need to manually clone the BIMBots repo under your FreeCAD's Mod folder. See the [README](../README.md) file for further instructions 16 | 17 | The easiest way to install the BIMBots plugin is via the Add-ons Manager in FreeCAD, found under menu **Tools -> Addon Manager**. Locate the BIMBots add-on in the list, click "install", restart FreeCAD. 18 | 19 | If you also have the **BIM Workbench** add-on installed, a BIMbots entry will appear under menu **Utils** inside the BIM Workbench. If not, you'll be able to use the BIMBots macro from the Macros dialog under menu **Macro -> Macros...**. It is also possible to create a toolbar button from any installed macro. Refer to the [FreeCAD documentation](https://www.freecadweb.org/wiki/Customize_Toolbars) to do that. 20 | 21 | ### Usage 22 | 23 | Upon launching the BIMBots macro, a panel opens in the Tasks area of FreeCAD, as shown in the image above, and the plugins immediately downloads a list of available servers from the [official BIMbots repository list](https://raw.githubusercontent.com/opensourceBIM/BIMserver-Repository/master/serviceproviders.json). This list is maintained by the BIMServer project and lists common addresses where BIMBots services can be found. If you have a BIMServer instance running locally on the default port, it will be autodetected here. 24 | 25 | The list of servers and their available services will be shown on the BIMBots panel. Hovering the mouse over a service displays additional information such as which format it accepts and delivers: 26 | 27 | ![server info](images/bimbots-ui-02.jpg) 28 | 29 | ### Managing servers and services 30 | 31 | Pressing the **Rescan services** button will open a sub-panel showing a couple of options: 32 | 33 | 34 | 35 | * **Auto-discover new services**: Queries the servers from the [official BIMbots repository list](https://raw.githubusercontent.com/opensourceBIM/BIMserver-Repository/master/serviceproviders.json) for services. Otherwise, only lists the servers that have been manually added (see below) 36 | * **Show unreachable providers**: Displays the servers that are present in the official list, or that have been manually, but that cannot be reached or don't provide a list of BIMBots services. Otherwise, these unavailable servers won't be shown 37 | * **Filter by category**: For future use, as BIMBots services can implement categories 38 | * **Max price**: For future use, as paid BIMBots services can exist 39 | * **Scan**: Updates the services list according to the above criteria 40 | * **Cancel**: Closes this sub-panel 41 | 42 | You can add custom BIMServer instances, or any other BIMBots provider that you have access to and know the URL, by pressing the **Add custom service provider** button, which opens another sub-panel: 43 | 44 | 45 | 46 | * **Name**: A custom name for the new server 47 | * **URL**: The server URL. BIMBots server URLs usually end with /servicelist which is the entry point where the server can be queried for available services. Here, you can put either the "pure" server URL (ex. http://www.myserver.com) or add the /servicelist (ex. http://www.myserver.com/servicelist). Even if you don't, both will be tested for service entry point. 48 | * **Add**: Adds the new server and rescans 49 | * **Cancel**: Closes this sub-panel 50 | 51 | You can remove any custom server you added this way by selecting it, then pressing the **Remove custom service provider** button. That button will only be enabled if you select a custom service provider. Service providers that were automatically added through the "Auto-discover new services" above cannot be removed. 52 | 53 | ![](images/bimbots-ui-05.jpg) 54 | 55 | ### Authenticating 56 | 57 | Before using a BIMBots service, you need to **authenticate** with that service. For that, you need to already have a user account on the server that provides the service. Authentication needs to happen only once. FreeCAD will remember authenticated services, and mark them with a green icon in the services list. 58 | 59 | To authenticate with a service, select it and press the **Authenticate** button. A sub-panel will open, and, simultaneously, your web browser will open and show the authentication page of the service: 60 | 61 | ![](images/bimbots-ui-07.jpg) 62 | 63 | The "Select service configuration" drop-down list should contain at least one entry (usually only one). If it doesn't, and you are unable to authenticate, you might need to enable this service from the same screen, under menu **User settings -> Internal services**. 64 | 65 | After clicking the **Authorize** button, youwill be given a token and a service URL: 66 | 67 | ![](images/bimbots-ui-08.jpg) 68 | 69 | These two values need to be copied and pasted back into FreeCAD, into the sub-panel that opened when you pressed the **Authenticate** button: 70 | 71 | 72 | 73 | * **Token**: The Token value obtained from the server web page above 74 | * **Service URL**: The service address obtained from the server web page above 75 | * **Save**: Saves the authentication values. This button will only be enabled when both fields are filled 76 | * **Cancel**: Cancels the operation and closes this sub-panel 77 | 78 | You can authenticate again with an already authenticated service, any number of times, by pressing the Authenticate button again. 79 | 80 | --- 81 | --- 82 | --- 83 | 84 | ### Running services 85 | 86 | Once you have authenticated with a service, you can run it on a FreeCAD model. You need to have a model already opened in FreeCAD, as the BIMBots plugin will act on the active document. 87 | 88 | Note that no service will actually modify your model. They will only deliver a report. It is therefore totally safe to test them on any of your models. 89 | 90 | BIMBots services can deliver many [data types](https://github.com/opensourceBIM/BIM-Bot-services/wiki/Schemas). The most common are JSON, simple text, or BCF files. JSON and text reports will be formatted and displayed to you upon receiving. BCF files get saved on your computer, but FreeCAD currently doesn't provide a way to visualise BCF files (this is being worked on). Hovering the mouse over a service will inform you of the type of data it will deliver. 91 | 92 | To run a service, select an authenticated service, and one of the three options below: 93 | 94 | 95 | 96 | * **All document objects**: Will send all objects from the current document to the BIMBots service. This is usually not what you want, as it will also include objects that are components of others, such as wall baselines or construction geometry. Use this option for testing purposes only. 97 | * **All visible objects**: Will send all objects currently visible in the 3D view. This is a quick way to check your whole model without having to care about a proper model structure. 98 | * **Selected objects**: This is usually the preferred option. If you select one or more container objects, such as groups, building parts, storeys, buildings or sites, all their contents will be added as well. If you are using a proper IFC-based model structure, you can usually only select the base site or building, and all the model contained in it will be sent together. 99 | * **Run service**: Saves the model objects obtained by the selection method above to a temporary IFC file, and sends this file to the selected service. This button will only be enabled if both an authenticated service and a selection method are highlighted. 100 | 101 | 102 | --- 103 | --- 104 | --- 105 | --- 106 | --- 107 | 108 | ### Reading service reports 109 | 110 | ![](images/bimbots-ui-11.jpg) 111 | 112 | If all went well, after a little time (depending on the size of your model), the plugin will receive a report and open a new panel to display the results: 113 | 114 | The data contained in the report depends on the BIMBots service. If the report is made of JSON or Text data, a new panel opens to display its contents. Blue entries can be double-clicked, and the according object will be selected in the FreeCAD document. If an IFC type is double-clicked, such as IfcWall, all objects of that type found in the FreeCAD document will be selected. 115 | 116 | If the report is a BCF file, that file is stored on your computer, and notifies you of the file location. At the moment, FreeCAD doesn't have support for visualising BCF files. 117 | 118 | The **Run another service** button will close the report panel and go back to the previous screen. 119 | 120 | ### Troubleshooting 121 | 122 | * **Cannot authenticate**: If the server fails to give a token and service address, the most likely cause is that the service you are requesting wasn't enabled for your user. Services installed on a BIMServer need to be enabled by each user wishing to use them. That can be done in the same web page, under menu **User settings -> Internal services**. 123 | 124 | * **Service returns "no response"**: The most common reason why a service couldn't be run is because there is a defect in your model that prevents it from being analysed. Try choosing the **Selected objects** option, and select only one simple object from your model, such as a wall or column. If that works, try repeating the operation selecting more objects, until identifying which objects are not accepted by the server. 125 | * **Service still returns "no response"**: Another reason why the server might reject your file is because the appropriate deserializer is not enabled for your user. Try accessing the server in a web browser (it can be done by pressing the "Authenticate" button as described above, navigate to menu **User settings -> Deserializers**, and add both Ifc2x3 and Ifc4 step deserializers. 126 | * **Still "no response"**: Try the debug options below. 127 | * **Debug options**: The BIMBots plugin can be made to display additional testing options to the list above, that can be useful to test if you suspect the plugin is not working correctly. To enable debug options, close the BIMBots plugin, and enter the following code in the FreeCAD Python console (found in FreeCAD under menu **View -> Panels**): 128 | 129 | ``` 130 | import bimbots 131 | bimbots.DEBUG = True 132 | bimbots.launchUI() 133 | ``` 134 | 135 | The BIMBots panel will be shown again, like upon normal use. However, additional options will be present in the object selection section: 136 | 137 | * **Choose IFC file**: This allows you to send an existing IFC file already present on your machine instead of the contents of the active FreeCAD document. Use this to test a service with a file that is known to work with the given BIMServer. 138 | * **Test payload**: This option will send a minimal IFC file that is guaranteed to work with any server. If this test fails, then there is likely a problem on the server that provides the service, or with your internet connection. 139 | * **Test output only**: This option doesn't send any data to any service, but opens the results panel and fills it with dummy result data. Try this if you suspect that the BIMBots interface is not working correctly. 140 | -------------------------------------------------------------------------------- /icons/BIM-Bots-header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/icons/BIM-Bots-header.png -------------------------------------------------------------------------------- /icons/BIM-Bots-validationchecker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/icons/BIM-Bots-validationchecker.png -------------------------------------------------------------------------------- /icons/BIM-Bots-viewer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/icons/BIM-Bots-viewer.png -------------------------------------------------------------------------------- /icons/Tango-Computer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 22 | 29 | 32 | 36 | 40 | 41 | 43 | 47 | 51 | 52 | 54 | 58 | 62 | 63 | 65 | 69 | 73 | 74 | 76 | 80 | 84 | 85 | 88 | 92 | 96 | 97 | 99 | 103 | 107 | 108 | 110 | 114 | 118 | 119 | 122 | 126 | 130 | 131 | 133 | 137 | 141 | 142 | 145 | 149 | 153 | 154 | 157 | 161 | 165 | 166 | 168 | 172 | 176 | 177 | 180 | 184 | 188 | 189 | 192 | 196 | 200 | 201 | 203 | 207 | 211 | 212 | 214 | 218 | 222 | 223 | 233 | 243 | 253 | 263 | 273 | 284 | 295 | 305 | 315 | 326 | 336 | 346 | 356 | 366 | 375 | 384 | 393 | 402 | 411 | 421 | 431 | 441 | 451 | 461 | 471 | 481 | 491 | 492 | 511 | 513 | 514 | 516 | image/svg+xml 517 | 519 | Computer 520 | 2005-03-08 521 | 522 | 523 | Jakub Steiner 524 | 525 | 526 | 527 | 528 | workstation 529 | computer 530 | node 531 | client 532 | 533 | 534 | 536 | http://jimmac.musichall.cz/ 537 | 538 | 540 | 542 | 544 | 546 | 547 | 548 | 549 | 553 | 563 | 573 | 583 | 593 | 598 | 608 | 615 | 620 | 625 | 629 | 634 | 639 | 649 | 654 | 659 | 663 | 667 | 671 | 676 | 680 | 685 | 695 | 705 | 715 | 725 | 735 | 739 | 740 | 741 | -------------------------------------------------------------------------------- /testfiles/test payload response.json: -------------------------------------------------------------------------------- 1 | { 2 | "classifications": [ 3 | { 4 | "references": [ 5 | { 6 | "name": "NO_CLASSIFICATION_REFERENCE", 7 | "numberOfObjects": 4 8 | } 9 | ], 10 | "name": "NO_CLASSIFICATION" 11 | } 12 | ], 13 | "aggregations": { 14 | "completeModel": { 15 | 16 | }, 17 | "perType": { 18 | "IfcSite": { 19 | "averageNumberOfRelations": 2, 20 | "averageNumberOfProperties": 0, 21 | "averageNumberOfPsets": 0, 22 | "numberOfObjects": 1 23 | }, 24 | "IfcWall": { 25 | "averageNumberOfTriangles": 12, 26 | "averageNumberOfRelations": 0, 27 | "averageNumberOfProperties": 0, 28 | "averageNumberOfPsets": 0, 29 | "numberOfObjects": 1 30 | }, 31 | "IfcBuildingStorey": { 32 | "averageNumberOfRelations": 1, 33 | "averageNumberOfProperties": 0, 34 | "averageNumberOfPsets": 0, 35 | "numberOfObjects": 1 36 | }, 37 | "IfcBuilding": { 38 | "averageNumberOfRelations": 2, 39 | "averageNumberOfProperties": 0, 40 | "averageNumberOfPsets": 0, 41 | "numberOfObjects": 1 42 | } 43 | }, 44 | "topTenMostComplexObjects": [ 45 | { 46 | "volumeM3": 0.400000000000008, 47 | "name": "Wall", 48 | "numberOfTriangles": 12, 49 | "trianglesPerM3": 30.0, 50 | "guid": "0T$pXcCuKHwQyL093rt3ko", 51 | "type": "IfcWall" 52 | }, 53 | { 54 | "guid": "0U1KiqCuKHwQyL093rt3ko", 55 | "type": "IfcBuilding", 56 | "name": "Default Building", 57 | "trianglesPerM3": 0.0 58 | }, 59 | { 60 | "guid": "0U1HEACuKHwQyL093rt3ko", 61 | "type": "IfcSite", 62 | "name": "Default Site", 63 | "trianglesPerM3": 0.0 64 | }, 65 | { 66 | "guid": "0U1NVkCuKHwQyL093rt3ko", 67 | "type": "IfcBuildingStorey", 68 | "name": "Default Storey", 69 | "trianglesPerM3": 0.0 70 | } 71 | ], 72 | "topTenMostProperties": [ 73 | { 74 | "numberOfProperties": 0, 75 | "guid": "0T$pXcCuKHwQyL093rt3ko", 76 | "type": "IfcWall", 77 | "name": "Wall" 78 | }, 79 | { 80 | "numberOfProperties": 0, 81 | "guid": "0U1KiqCuKHwQyL093rt3ko", 82 | "type": "IfcBuilding", 83 | "name": "Default Building" 84 | }, 85 | { 86 | "numberOfProperties": 0, 87 | "guid": "0U1HEACuKHwQyL093rt3ko", 88 | "type": "IfcSite", 89 | "name": "Default Site" 90 | }, 91 | { 92 | "numberOfProperties": 0, 93 | "guid": "0U1NVkCuKHwQyL093rt3ko", 94 | "type": "IfcBuildingStorey", 95 | "name": "Default Storey" 96 | } 97 | ] 98 | }, 99 | "project": { 100 | "units": [ 101 | { 102 | "unitType": "LENGTHUNIT", 103 | "prefix": "NULL", 104 | "name": "METRE" 105 | }, 106 | { 107 | "unitType": "AREAUNIT", 108 | "prefix": "NULL", 109 | "name": "SQUARE_METRE" 110 | }, 111 | { 112 | "unitType": "VOLUMEUNIT", 113 | "prefix": "NULL", 114 | "name": "CUBIC_METRE" 115 | }, 116 | { 117 | "unitType": "PLANEANGLEUNIT", 118 | "name": "DEGREE" 119 | } 120 | ], 121 | "guid": "2cbe6e88_33a0_4b80_a96", 122 | "name": "Unnamed", 123 | "sites": [ 124 | { 125 | "buildings": [ 126 | { 127 | "guid": "0U1KiqCuKHwQyL093rt3ko", 128 | "storeys": [ 129 | { 130 | "spaces": [ 131 | 132 | ], 133 | "guid": "0U1NVkCuKHwQyL093rt3ko", 134 | "name": "Default Storey", 135 | "totalNumberOfObjects": 0 136 | } 137 | ], 138 | "name": "Default Building" 139 | } 140 | ], 141 | "guid": "0U1HEACuKHwQyL093rt3ko", 142 | "name": "Default Site" 143 | } 144 | ] 145 | }, 146 | "header": { 147 | "preProcessorVersion": "", 148 | "description": [ 149 | "" 150 | ], 151 | "author": [ 152 | "" 153 | ], 154 | "timeStamp": 1550498433000, 155 | "filename": "", 156 | "originatingSystem": "BIMserver", 157 | "implementationLevel": "2;1", 158 | "organization": [ 159 | "" 160 | ], 161 | "schemaVersion": "IFC2X3", 162 | "authorization": "" 163 | }, 164 | "materials": [ 165 | { 166 | "name": "NO_MATERIAL", 167 | "nrOfProducts": 4 168 | } 169 | ], 170 | "checks": { 171 | "hasCubeNearZero": false 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /testfiles/test payload.ifc: -------------------------------------------------------------------------------- 1 | ISO-10303-21; 2 | HEADER; 3 | FILE_DESCRIPTION ((''), '2;1'); 4 | FILE_NAME ('', '2019-02-18T11:00:33', (''), (''), '', 'FreeCAD', ''); 5 | FILE_SCHEMA (('IFC2X3')); 6 | ENDSEC; 7 | DATA; 8 | #1=IFCPERSON($,$,'Yorik van Havre',$,$,$,$,$); 9 | #2=IFCORGANIZATION($,'uncreated.net',$,$,$); 10 | #3=IFCPERSONANDORGANIZATION(#1,#2,$); 11 | #4=IFCAPPLICATION(#2,'0.18 build 15866 (Git)','FreeCAD','118df2cf_ed21_438e_a41'); 12 | #5=IFCOWNERHISTORY(#3,#4,$,.ADDED.,$,#3,#4,1550498243); 13 | #6=IFCDIRECTION((1.,0.,0.)); 14 | #7=IFCDIRECTION((0.,0.,1.)); 15 | #8=IFCCARTESIANPOINT((0.,0.,0.)); 16 | #9=IFCAXIS2PLACEMENT3D(#8,#7,#6); 17 | #10=IFCDIRECTION((0.,1.,0.)); 18 | #11=IFCGEOMETRICREPRESENTATIONCONTEXT('Plan','Model',3,1.E-05,#9,#10); 19 | #12=IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0); 20 | #13=IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.); 21 | #14=IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.); 22 | #15=IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.); 23 | #16=IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.); 24 | #17=IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.017453292519943295),#16); 25 | #18=IFCCONVERSIONBASEDUNIT(#12,.PLANEANGLEUNIT.,'DEGREE',#17); 26 | #19=IFCUNITASSIGNMENT((#13,#14,#15,#18)); 27 | #20=IFCPROJECT('2cbe6e88_33a0_4b80_a96',#5,'Unnamed',$,$,$,$,(#11),#19); 28 | #21=IFCCARTESIANPOINT((0.5,-0.100000000000004)); 29 | #22=IFCCARTESIANPOINT((-0.5,-0.100000000000007)); 30 | #23=IFCCARTESIANPOINT((-0.5,0.0999999999999971)); 31 | #24=IFCCARTESIANPOINT((0.5,0.1)); 32 | #25=IFCPOLYLINE((#21,#22,#23,#24,#21)); 33 | #26=IFCARBITRARYCLOSEDPROFILEDEF(.AREA.,$,#25); 34 | #27=IFCCARTESIANPOINT((0.5,-0.1,0.)); 35 | #28=IFCAXIS2PLACEMENT3D(#27,#7,#6); 36 | #29=IFCEXTRUDEDAREASOLID(#26,#28,#7,2.); 37 | #30=IFCCOLOURRGB($,1.,1.,1.); 38 | #31=IFCSURFACESTYLERENDERING(#30,$,$,$,$,$,$,$,.FLAT.); 39 | #32=IFCSURFACESTYLE($,.BOTH.,(#31)); 40 | #33=IFCPRESENTATIONSTYLEASSIGNMENT((#32)); 41 | #34=IFCSTYLEDITEM(#29,(#33),$); 42 | #35=IFCLOCALPLACEMENT($,#9); 43 | #36=IFCSHAPEREPRESENTATION(#11,'Body','SweptSolid',(#29)); 44 | #37=IFCPRODUCTDEFINITIONSHAPE($,$,(#36)); 45 | #38=IFCWALL('0T$pXcCuKHwQyL093rt3ko',#5,'Wall','',$,#35,#37,$,$); 46 | #39=IFCSITE('0U1HEACuKHwQyL093rt3ko',#5,'Default Site','',$,$,$,$,.ELEMENT.,$,$,$,$,$); 47 | #40=IFCRELAGGREGATES('0U1JU4CuKHwQyL093rt3ko',#5,'ProjectLink','',#20,(#39)); 48 | #41=IFCBUILDING('0U1KiqCuKHwQyL093rt3ko',#5,'Default Building','',$,$,$,$,.ELEMENT.,$,$,$); 49 | #42=IFCRELAGGREGATES('0U1LogCuKHwQyL093rt3ko',#5,'SiteLink','',#39,(#41)); 50 | #43=IFCBUILDINGSTOREY('0U1NVkCuKHwQyL093rt3ko',#5,'Default Storey','',$,$,$,$,.ELEMENT.,$); 51 | #44=IFCRELAGGREGATES('0U1OX2CuKHwQyL093rt3ko',#5,'DefaultStoreyLink','',#41,(#43)); 52 | #45=IFCRELCONTAINEDINSPATIALSTRUCTURE('0U1PPmCuKHwQyL093rt3ko',#5,'UnassignedObjectsLink','',(#38),#43); 53 | ENDSEC; 54 | END-ISO-10303-21; 55 | -------------------------------------------------------------------------------- /testfiles/test response.bcf.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/opensourceBIM/BIMbots-FreeCAD/595f3ee006e701b864ad8a92030f8af733befc7b/testfiles/test response.bcf.zip -------------------------------------------------------------------------------- /translations/README.md: -------------------------------------------------------------------------------- 1 | ## How to translate BIMBots 2 | 3 | This folder contains one 'master' translation file called bimbots.ts which contains all strings 4 | found in the code. It is generated with: 5 | 6 | `pylupdate5 *.py *.ui -ts translations/bimbots.ts` 7 | 8 | To translate BIMBots to your language, (FreeCAD plugin only, the Python 9 | module functions stay in english), download the bimbots.ts file, and translate it 10 | using the [Qt Linguist](https://doc.qt.io/qt-5/linguist-translators.html) tool. 11 | 12 | There are also online translation services which accept .ts files, you can use that too. 13 | 14 | Once done, save your translation as another file, by adding your language (and 15 | optionally region if applicable) to the filename, ex: 16 | 17 | bimbots-fr.ts (for French) or 18 | bimbots-fr_CA.ts (for Canadian French) 19 | 20 | Language and region codes are easy to [find on the net](https://www.fincher.org/Utilities/CountryLanguageList.shtml). 21 | 22 | Once done, you need to compile your .ts file as a .qm file with: 23 | 24 | `lrelease bimbots-fr.ts -qm bimbots-fr.qm` 25 | 26 | Once you have both a .ts and a.qm file for your language, create a 27 | [pull request](https://help.github.com/en/articles/about-pull-requests) here 28 | and we'll include it! 29 | 30 | 31 | -------------------------------------------------------------------------------- /translations/bimbots.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BIMBots 5 | 6 | 7 | Getting services 8 | 9 | 10 | 11 | 12 | saved 13 | 14 | 15 | 16 | 17 | autodiscovered 18 | 19 | 20 | 21 | 22 | Authenticated 23 | 24 | 25 | 26 | 27 | Unreachable 28 | 29 | 30 | 31 | 32 | Test output only 33 | 34 | 35 | 36 | 37 | Removal warning 38 | 39 | 40 | 41 | 42 | Remove provider 43 | 44 | 45 | 46 | 47 | This cannot be undone. 48 | 49 | 50 | 51 | 52 | Unable to open a web browser. Please paste the following URL in your web browser 53 | 54 | 55 | 56 | 57 | Preparing 58 | 59 | 60 | 61 | 62 | Test payload 63 | 64 | 65 | 66 | 67 | Sending data 68 | 69 | 70 | 71 | 72 | Choose IFC file 73 | 74 | 75 | 76 | 77 | Choose an existing IFC file 78 | 79 | 80 | 81 | 82 | IFC files (*.ifc) 83 | 84 | 85 | 86 | 87 | Saving IFC file 88 | 89 | 90 | 91 | 92 | Selected objects 93 | 94 | 95 | 96 | 97 | All visible objects 98 | 99 | 100 | 101 | 102 | BCF results saved as: 103 | 104 | 105 | 106 | 107 | BCF viewing is not yet implemented. 108 | 109 | 110 | 111 | 112 | Results 113 | 114 | 115 | 116 | 117 | Error: No results obtained 118 | 119 | 120 | 121 | 122 | Empty response 123 | 124 | 125 | 126 | 127 | The server didn't send a valid response. There can be many reasons to this, but the most likely is that the IFC file generated from your model wasn't accepted by the server. Try working with only a couple of selected objects first, to see if the service is responding correctly. 128 | 129 | 130 | 131 | 132 | Form 133 | 134 | 135 | BIMbots 136 | 137 | 138 | 139 | 140 | BIMbots logo 141 | 142 | 143 | 144 | 145 | connecting... 146 | 147 | 148 | 149 | 150 | Cancel the current connection 151 | 152 | 153 | 154 | 155 | Cancel 156 | 157 | 158 | 159 | 160 | Available services 161 | 162 | 163 | 164 | 165 | Tries to obtain a list of available services from all registered servers 166 | 167 | 168 | 169 | 170 | Rescan services 171 | 172 | 173 | 174 | 175 | Service discovery options: 176 | 177 | 178 | 179 | 180 | Auto-discover new services 181 | 182 | 183 | 184 | 185 | Show unreachable providers 186 | 187 | 188 | 189 | 190 | Filter by category... 191 | 192 | 193 | 194 | 195 | max price: 196 | 197 | 198 | 199 | 200 | 0 USD 201 | 202 | 203 | 204 | 205 | Scan 206 | 207 | 208 | 209 | 210 | Adds a new entry to the list of known service providers 211 | 212 | 213 | 214 | 215 | Add custom service provider 216 | 217 | 218 | 219 | 220 | Add custom service provider: 221 | 222 | 223 | 224 | 225 | Name: 226 | 227 | 228 | 229 | 230 | URL: 231 | 232 | 233 | 234 | 235 | A custom name to identify this services provider 236 | 237 | 238 | 239 | 240 | The URL of this services provider 241 | 242 | 243 | 244 | 245 | Add this server to the list of known service providers 246 | 247 | 248 | 249 | 250 | Add 251 | 252 | 253 | 254 | 255 | Cancels the operation 256 | 257 | 258 | 259 | 260 | Removes a selected service from the list above 261 | 262 | 263 | 264 | 265 | Remove custom service provider 266 | 267 | 268 | 269 | 270 | <html><head/><body><p>Authenticates with a selected service. Opens a web browser window.</p><p><br/></p><p>If unable to obtain a token, you might need to add internal services to your account under &quot;User Settings&quot; in the BIMServer panel </p></body></html> 271 | 272 | 273 | 274 | 275 | Authenticate 276 | 277 | 278 | 279 | 280 | Paste authentication credentials here: 281 | 282 | 283 | 284 | 285 | Service URL: 286 | 287 | 288 | 289 | 290 | The service URL returned by the authentication process 291 | 292 | 293 | 294 | 295 | Token: 296 | 297 | 298 | 299 | 300 | The token returned by the authentication process 301 | 302 | 303 | 304 | 305 | Saves the authentication credentials 306 | 307 | 308 | 309 | 310 | Save 311 | 312 | 313 | 314 | 315 | Run selected service 316 | 317 | 318 | 319 | 320 | Run service on: 321 | 322 | 323 | 324 | 325 | All document objects 326 | 327 | 328 | 329 | 330 | <html><head/><body><p>Sends all the objects contained in the active document to the selected service. Warning, all the inner object components wil be sent too, this is usually not what you want. Use for testing purposes only.</p></body></html> 331 | 332 | 333 | 334 | 335 | All visible objects 336 | 337 | 338 | 339 | 340 | <html><head/><body><p>Sends all the visible objects to the selected service. All the needed components will be gathered automatically. Choose this option if your model doesn't use IFC building structures such as Sites, Buildings or Storeys</p></body></html> 341 | 342 | 343 | 344 | 345 | Selected objects 346 | 347 | 348 | 349 | 350 | <html><head/><body><p>Sends the selected objects to the given service. All the necessary components will be gathered authomatically. You can select only one or more groups or sites, buildings or storeys, all their child objects will be collected too. This is usually the best option. </p></body></html> 351 | 352 | 353 | 354 | 355 | Choose IFC file 356 | 357 | 358 | 359 | 360 | <html><head/><body><p>Allows you to select an existing IFC file to send</p></body></html> 361 | 362 | 363 | 364 | 365 | Test payload 366 | 367 | 368 | 369 | 370 | <html><head/><body><p>Sends a very simple test payload IFC to the service, that is guaranteed to work. Use this to test if the service works correctly.</p></body></html> 371 | 372 | 373 | 374 | 375 | Test output only 376 | 377 | 378 | 379 | 380 | <html><head/><body><p>Runs the selected service and displays the results</p><p><br/></p><p>If unable to obtain a response, you might need to add deserializers to your account under &quot;User Settings&quot; in the BIMServer panel </p></body></html> 381 | 382 | 383 | 384 | 385 | Run service 386 | 387 | 388 | 389 | 390 | Results 391 | 392 | 393 | 394 | 395 | value 396 | 397 | 398 | 399 | 400 | Closes the results screen and re-shows the service options 401 | 402 | 403 | 404 | 405 | Run other service 406 | 407 | 408 | 409 | 410 | <html><head/><body><p><a href="https://github.com/opensourceBIM/BIMbots-FreeCAD/blob/master/doc/ui-documentation.md"><span>BIMbots plugin documentation</span></a></p></body></html> 411 | 412 | 413 | 414 | 415 | --------------------------------------------------------------------------------