├── .gitignore ├── LICENSE ├── LPF_DGS_bowtie.py ├── README.md ├── __init__.py ├── examples ├── .gitignore ├── LPF_DGS_bowtie_15.py ├── LPF_DGS_bowtie_7.py ├── README.md ├── capacitor_ground_cutout.py ├── field_replaceable_oshpark_flex.py ├── idbpf_tap_oshpark │ ├── README.md │ └── filter.py ├── images │ └── vert_connector_ms_oshpark.png ├── microstrip.py ├── microstrip_PML.py ├── miter.py ├── openems ├── semi_rigid_047_oshpark_flex.py ├── semi_rigid_086_oshpark_flex.py ├── sma_el.py ├── sma_th.py ├── sss_idbpf.py ├── stripline │ └── line.py ├── stripline_ecbpf.py ├── stripline_ss100.py ├── transition.py ├── vert_connector_ms_oshpark.kicad_mod ├── vert_connector_ms_oshpark.py ├── via_1_3_oshpark.py ├── via_1_4_oshpark.py ├── wilkinson_1.py ├── wilkinson_2.py └── wilkinson_3.py ├── geometries.py ├── idbpf.py ├── idbpf_tapped.py ├── kicadfpwriter.py ├── miter.py ├── polygon.py ├── ratrace_folded.py └── wilkinson.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | *~ 3 | *.kicad_mod 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Packages 9 | *.egg 10 | *.egg-info 11 | dist 12 | build 13 | eggs 14 | parts 15 | bin 16 | var 17 | sdist 18 | develop-eggs 19 | .installed.cfg 20 | lib 21 | lib64 22 | __pycache__ 23 | 24 | # Installer logs 25 | pip-log.txt 26 | 27 | # Unit test / coverage reports 28 | .coverage 29 | .tox 30 | nosetests.xml 31 | 32 | # Translations 33 | *.mo 34 | 35 | # Mr Developer 36 | .mr.developer.cfg 37 | .project 38 | .pydevproject 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /LPF_DGS_bowtie.py: -------------------------------------------------------------------------------- 1 | from openems import Box, Port, Polygon, Metal, arc 2 | import numpy as np 3 | 4 | def generate( 5 | em, 6 | sub, 7 | mask, 8 | min_width, # minimum copper width, typically design rule minimum 9 | cutout_width, # width of ground plane cutouts 10 | inductors, 11 | capacitors, 12 | z, # bottom of [air below, bottom metal, substrate, top metal, air above, lid] 13 | port_length, 14 | ms_width, 15 | box_width, 16 | half_fan_angle=0.25*np.pi): 17 | 18 | em.mesh.AddLine('z', z[0]) # air above, below 19 | em.mesh.AddLine('z', z[5]) 20 | em.mesh.AddLine('z', 0.5*(z[2]+z[3])) 21 | em.mesh.AddLine('z', 0.25*(3*z[2]+z[3])) 22 | em.mesh.AddLine('z', 0.25*(z[2]+3*z[3])) 23 | em.mesh.AddLine('y', -0.5*min_width) 24 | em.mesh.AddLine('y', 0.5*min_width) 25 | 26 | box_length = 2.0*(np.sum(inductors) + capacitors[0] + 0.5e-3) 27 | 28 | x0 = -0.5*box_length 29 | x1 = x0 + port_length 30 | x2 = x1 + ms_width 31 | 32 | yb = 0.5*cutout_width 33 | yo = -0.1e-3 # overlap 34 | 35 | pec = Metal(em, 'pec') 36 | 37 | # substrate 38 | start = np.array([ 0.5*box_length, 0.5*box_width, z[2]]) 39 | stop = np.array([-0.5*box_length, -0.5*box_width, z[3]]) 40 | Box(sub, 1, start, stop) 41 | # solder mask 42 | if mask != None: 43 | start[2] = z[3] + 25e-6 44 | Box(mask, 1, start, stop) 45 | 46 | # top copper polygon 47 | points = np.zeros((6+10*len(inductors),2)) 48 | points[0] = [x1, 0.5*ms_width] 49 | x = -np.sum(inductors) 50 | points[1] = [x - 0.5*ms_width, 0.5*ms_width] 51 | points[2:10] = arc(x, 0, capacitors[0], 52 | 0.5*np.pi+half_fan_angle, 53 | 0.5*np.pi-half_fan_angle, 54 | npoints = 8) 55 | points[10] = [x + 0.5*min_width, 0.5*min_width] 56 | i = 11 57 | x += inductors[0] 58 | for j in range(1, len(inductors)): 59 | points[i+0] = [x-0.5*min_width, 0.5*min_width] 60 | points[i+1:i+9] = arc(x, 0, capacitors[j], 61 | 0.5*np.pi+half_fan_angle, 62 | 0.5*np.pi-half_fan_angle, 63 | npoints = 8) 64 | points[i+9] = [x+0.5*min_width, 0.5*min_width] 65 | i += 10 66 | x += inductors[j] 67 | # center cap 68 | points[i+0] = [-0.5*min_width, 0.5*min_width] 69 | points[i+1:i+5] = arc(0, 0, capacitors[-1], 70 | 0.5*np.pi+half_fan_angle, 71 | 0.5*np.pi + 0.001, npoints = 4) 72 | print(points) 73 | points = np.concatenate((points, points[::-1]*[-1,1])) 74 | points = np.concatenate((points, points[::-1]*[1,-1])) 75 | Polygon(pec, points, z[3:5], priority=9, pcb_layer = 'F.Cu') 76 | 77 | # ground plane 78 | gpec = Metal(em, 'ground_plane') 79 | points = np.zeros((2+6*len(inductors),2)) 80 | points[0] = [x0, 0.5*box_width] 81 | points[1] = [x0, yo] 82 | i = 2 83 | x = -np.sum(inductors) 84 | for l in inductors: 85 | hmw = 0.5*min_width 86 | xl = x + hmw 87 | points[i+0] = [xl,yo] 88 | points[i+1] = [xl,hmw] 89 | xl = x + yb 90 | points[i+2] = [xl,yb] 91 | xl = x + l - yb 92 | points[i+3] = [xl,yb] 93 | xl = x + l - hmw 94 | points[i+4] = [xl,hmw] 95 | points[i+5] = [xl,yo] 96 | i += 6 97 | x += l 98 | print("ground plane",points) 99 | for ym in [1,-1]: 100 | Polygon(gpec, 101 | points = [1,ym] * np.concatenate((points, points[::-1]*[-1,1])), 102 | priority = 9, 103 | elevation = z[1:3], 104 | pcb_layer = 'B.Cu', 105 | is_custom_pad = True, 106 | x=0, 107 | y=ym*(yb+0.1e-3), 108 | pad_name='3', 109 | ) 110 | 111 | for (xm,padname) in [(-1,2),(1,1)]: 112 | # main line ports 113 | start = [x0*xm, -0.5*ms_width, z[3]] 114 | stop = [x1*xm, 0.5*ms_width, z[4]] 115 | Port(em, start, stop, direction='x', z=50) 116 | # pads 117 | start[0] = x2*xm 118 | l1 = Box(pec, 9, start, stop, padname=padname) 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyopenems 2 | 3 | A wrapper for the OpenEMS FDTD solver adding Kicad footprint generation for the simulated object. 4 | 5 | This is mostly an internal tool at Harmon Instruments and is likely to have the interfaces change at any time. 6 | 7 | ## [Examples](examples) 8 | 9 | ## Dependencies 10 | ### [OpenEMS](http://openems.de) > 0.35 with Python support. 11 | 12 | Install on Debian Buster: 13 | 14 | There is currently no installer, you need to place the repo somewhere in your python path. 15 | 16 | ```bash 17 | sudo apt build-dep openems 18 | sudo apt install cython3 build-essential cython3 python3-numpy python3-matplotlib 19 | sudo apt install python3-scipy python3-h5py 20 | 21 | git clone https://github.com/thliebig/openEMS-Project.git 22 | cd openEMS-Project 23 | git submodule init 24 | git submodule update 25 | export OPENEMS=$HOME/software/openems 26 | ./update_openEMS.sh $OPENEMS 27 | cd CSXCAD/python; python3 setup.py build_ext -I$OPENEMS/include -L$OPENEMS/lib -R$OPENEMS/lib; sudo python3 setup.py install; cd ../.. 28 | cd openEMS/python; python3 setup.py build_ext -I$OPENEMS/include -L$OPENEMS/lib -R$OPENEMS/lib; sudo python3 setup.py install; cd ../.. 29 | ``` 30 | 31 | Install on Ubuntu 18.04 (user submitted): 32 | 33 | instead of `sudo apt build-dep openems` 34 | 35 | ```bash 36 | sudo apt install libtinyxml-dev libhdf5-serial-dev libcgal-dev vtk6 libvtk6-qt-dev 37 | sudo python3 -m pip install --upgrade pip 38 | sudo python3 -m pip install vtk scipy matplotlib h5py 39 | ``` -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os, tempfile 3 | import numpy as np 4 | from scipy.constants import pi, c, epsilon_0, mu_0 5 | mm = 0.001 6 | import matplotlib.pyplot 7 | import openems.kicadfpwriter 8 | 9 | from CSXCAD import ContinuousStructure 10 | from openEMS import openEMS 11 | 12 | np.set_printoptions(precision=8) 13 | 14 | # convert an array of complex numbers to an array of [x,y] 15 | def complex_to_xy(a): 16 | rv = np.zeros((len(a),2)) 17 | for i in range(len(a)): 18 | rv[i][0] = a[i].real 19 | rv[i][1] = a[i].imag 20 | return rv 21 | 22 | # generate an array of [x,y] for an arc 23 | def arc(x, y, r, a0, a1, npoints=32): 24 | angles = np.linspace(a0,a1,npoints) 25 | return complex_to_xy(r*np.exp(1j*angles) + x + 1j*y) 26 | 27 | def mirror(point, axes): 28 | retval = np.array(point).copy() 29 | if 'x' in axes: 30 | retval *= np.array([-1,1,1]) 31 | if 'y' in axes: 32 | retval *= np.array([1,-1,1]) 33 | if 'z' in axes: 34 | retval *= np.array([1,1,-1]) 35 | return retval 36 | 37 | def db_angle(s): 38 | logmag = float(20.0*np.log10(np.abs(s))) 39 | angle = float(180.0*np.angle(s)/pi) 40 | return "{:>12f} {:>12f}".format(logmag, angle) 41 | 42 | def save_s1p(f, s11, filename, z): 43 | fdata = "# GHz S DB R {}\n".format(z) 44 | for i in range(len(f)): 45 | fdata += "{0:>12f} {1}\n".format(f[i]/1e9, db_angle(s11[i])) 46 | with open(filename, "w") as f: 47 | f.write(fdata) 48 | 49 | def save_s2p_symmetric(f, s11, s21, filename, z): 50 | print("warning: save_s2p_symmetric is only valid for a symmetric 2 port") 51 | fdata = "# GHz S DB R {}\n".format(z) 52 | for i in range(len(f)): 53 | fdata += "{0:>12f} {1} {2} {2} {1}\n".format(f[i]/1e9, db_angle(s11[i]), db_angle(s21[i])) 54 | with open(filename, "w") as f: 55 | f.write(fdata) 56 | 57 | class Material(): 58 | def __init__(self, em, name): 59 | self.lossy = False 60 | self.em = em 61 | self.name = name 62 | def AddBox(self, start, stop, priority, **kwargs): 63 | return Box(self, start=start, stop=stop, priority=priority, **kwargs) 64 | def AddPolygon(self, **kwargs): 65 | return Polygon(self, **kwargs) 66 | def AddCylinder(self, start, stop, radius, priority): 67 | return Cylinder(self, priority, start, stop, radius) 68 | 69 | class Dielectric(Material): 70 | def __init__(self, em, name, eps_r=1.0, kappa = 0, ur=1.0, tand=0.0, fc = 0): 71 | if tand > 0.0: 72 | kappa = tand * 2*pi*fc * epsilon_0 * eps_r 73 | self.material = em.CSX.AddMaterial(name, epsilon = eps_r, kappa = kappa, mue = ur) 74 | self.em = em 75 | self.name = name 76 | self.eps_r = eps_r 77 | self.ur = ur 78 | self.kappa = kappa 79 | self.type = 'dielectric' 80 | self.lossy = False # metal loss 81 | # magnetic loss 82 | 83 | class LumpedElement(Material): 84 | """ element_type = 'R' or 'C' or 'L' """ 85 | def __init__(self, em, name=None, element_type='R', value=50.0, direction = 'x'): 86 | self.em = em 87 | self.name = self.em.get_name(name) 88 | self.element_type = element_type 89 | self.value = value 90 | self.direction = direction 91 | self.material = em.CSX.AddLumpedElement(name=self.name, caps=False, ny = self.direction, R=self.value) 92 | 93 | class Metal(Material): 94 | def __init__(self, em, name): 95 | self.lossy = False 96 | self.em = em 97 | self.name = name 98 | self.type = 'metal' 99 | self.material=em.CSX.AddMetal(name) 100 | 101 | class LossyMetal(Material): 102 | def __init__(self, em, name, conductivity=56e6, frequency=None, thickness=None, ur=1.0): 103 | self.conductivity = conductivity 104 | if not thickness: 105 | # compute the skin depth 106 | self.thickness = np.sqrt((2.0/conductivity)/(2*pi*frequency*mu_0*ur)) 107 | print('thickness = {}'.format(self.thickness)) 108 | else: 109 | self.thickness = thickness 110 | self.em = em 111 | self.name = name 112 | self.type = 'metal' 113 | self.lossy = True 114 | self.material = em.CSX.AddConductingSheet(name, conductivity=self.conductivity, thickness=self.thickness) 115 | 116 | class Object(): 117 | def generate_kicad(self, g): 118 | pass 119 | 120 | from .polygon import Polygon 121 | 122 | class Box(Object): 123 | def __init__(self, material, priority, start, stop, padname = '1', pcb_layer='F.Cu', mirror='', mesh=True): 124 | mirror = [-1 if 'x' in mirror else 1, -1 if 'y' in mirror else 1, -1 if 'z' in mirror else 1] 125 | self.priority = priority 126 | self.material = material 127 | self.start = np.array(start) * mirror 128 | self.stop = np.array(stop) * mirror 129 | self.em = material.em 130 | self.name = self.em.get_name(None) 131 | self.padname = padname 132 | self.layer = pcb_layer 133 | self.em.objects[self.name] = self 134 | self.mesh = mesh 135 | def generate_kicad(self, g): 136 | if self.material.__class__.__name__ == 'Dielectric': 137 | return 138 | if self.padname == None: 139 | return 140 | if self.padname == 'poly': # use a polygon rather than a pad 141 | x1 = self.start[0] 142 | x2 = self.stop[0] 143 | y1 = self.start[1] 144 | y2 = self.stop[1] 145 | points = np.array([[x1,y1],[x2,y1],[x2,y2],[x1,y2]]) 146 | g.add_polygon(points = 1000.0 * points, layer = self.layer, width = 1e-6) 147 | return 148 | g.add_pad(self.padname, 149 | layer = self.layer, 150 | x = 500.0 * (self.start[0] + self.stop[0]), # mm 151 | y = 500.0 * (self.start[1] + self.stop[1]), # mm 152 | xsize = 1000.0 * abs(self.start[0] - self.stop[0]), # mm 153 | ysize = 1000.0 * abs(self.start[1] - self.stop[1])) 154 | def generate_octave(self): 155 | self.material.material.AddBox(start=self.start, stop=self.stop, 156 | priority=self.priority) 157 | if self.mesh: 158 | for vertex in [self.start, self.stop]: 159 | self.em.mesh.AddLine('x', vertex[0]) 160 | self.em.mesh.AddLine('y', vertex[1]) 161 | self.em.mesh.AddLine('z', vertex[2]) 162 | 163 | class Cylinder(Object): 164 | def __init__(self, material, priority, start, stop, radius): 165 | self.material = material 166 | self.start = np.array(start) 167 | self.stop = np.array(stop) 168 | self.em = material.em 169 | self.name = self.em.get_name(None) 170 | self.radius = radius 171 | self.padname = '1' 172 | self.priority = priority 173 | self.em.objects[self.name] = self 174 | def generate_octave(self): 175 | self.material.material.AddCylinder(priority=self.priority, 176 | start=self.start, 177 | stop=self.stop, 178 | radius=self.radius) 179 | 180 | class Via(Object): 181 | """PCB via in Z direction 182 | em = OpenEMS instance 183 | name = any string for reference 184 | material = a string matching a defined material (see Material class) 185 | priority = integer, when objects overlap, higher takes precedence 186 | x, y = position 187 | z[0] = [barrel top, barrel bottom] 188 | z[1:] = [pad top, pad bottom] 189 | drill radius 190 | pad rad 191 | """ 192 | def __init__(self, material, priority, x, y, z, drillradius, padradius, padname='1', 193 | wall_thickness=0): 194 | self.material = material 195 | self.priority = priority 196 | self.x = x 197 | self.y = y 198 | self.z = z 199 | self.drillradius = drillradius 200 | self.wall_thickness = wall_thickness 201 | self.padradius = padradius 202 | self.em = material.em 203 | self.name = self.em.get_name(None) 204 | self.padname = padname 205 | self.em.objects[self.name] = self 206 | def generate_kicad(self, g): 207 | g.add_pad(x = self.x * 1000.0, 208 | y = self.y * 1000.0, 209 | diameter = self.padradius * 2000.0, # footgen uses mm 210 | drill = self.drillradius * 2000.0, 211 | shape = "circle", 212 | name = self.padname) 213 | def generate_octave(self): 214 | start = [self.x + self.em.via_offset_x, self.y + self.em.via_offset_y, self.z[0][0]] 215 | stop = [self.x + self.em.via_offset_x, self.y + self.em.via_offset_y, self.z[0][1]] 216 | self.material.material.AddCylinder(start=start, stop=stop, priority=self.priority, 217 | radius = self.drillradius + self.wall_thickness) 218 | for z in self.z[1:]: 219 | start = [self.x, self.y, z[0]] 220 | stop = [self.x, self.y, z[1]] 221 | self.material.material.AddCylinder(start=start, stop=stop, priority=self.priority, radius = self.padradius) 222 | 223 | class RoundPad(Object): 224 | """PCB pad in Z direction 225 | em = OpenEMS instance 226 | material = a string matching a defined material (see Material class) 227 | priority = integer, when objects overlap, higher takes precedence 228 | x, y = position 229 | z[0] = [barrel top, barrel bottom] 230 | z[1:] = [pad top, pad bottom] 231 | drill radius 232 | pad rad 233 | """ 234 | def __init__(self, em, material, priority, x, y, z, padradius, padname='1'): 235 | self.material = material 236 | self.priority = priority 237 | self.x = x 238 | self.y = y 239 | self.z = z 240 | self.padradius = padradius 241 | self.em = em 242 | self.name = self.em.get_name(None) 243 | self.padname = padname 244 | em.objects[self.name] = self 245 | def generate_kicad(self, g): 246 | g.add_pad(x = self.x * 1000.0, 247 | y = self.y * 1000.0, 248 | diameter = self.padradius * 2000.0, # footgen uses mm 249 | mask_clearance = 0.0, 250 | shape = "circle", 251 | name = self.padname) 252 | def generate_octave(self): 253 | self.material.material.AddCylinder(start = [self.x, self.y, self.z[0]], 254 | stop = [self.x, self.y, self.z[1]], 255 | priority=self.priority, 256 | radius = self.padradius) 257 | 258 | def Resistor(em, origin=np.array([0,0,0]), direction='x', value=100.0, invert=False, priority=9, dielectric=None, metal=None, element_down=False, size='0201', thickness=None, FC=False): 259 | zm = -1 if invert else 1 260 | def orient(x): 261 | if not 'y' in direction: 262 | return np.array([x[1], x[0], x[2]]) 263 | return x 264 | if thickness is None: 265 | if size == '0402': 266 | thickness = 0.35e-3 267 | thickness = 0.25e-3 268 | x1 = 0.15e-3 269 | y1 = 0.3e-3 270 | y3 = 125e-6 271 | x2 = 0.1e-3 # element half width 272 | if size == '0402': 273 | x1 = 0.25e-3 274 | y1 = 0.5e-3 275 | y3 = 250e-6 276 | x2 = 0.2e-3 277 | y2 = y1-30e-6 278 | 279 | """ currently only supports 'x', 'y' for direction """ 280 | element = LumpedElement( 281 | em, name=em.get_name(None), element_type='R', value=value, direction=direction) 282 | # resistor end caps 283 | start = np.array([-x1, -y1, 0]) 284 | stop = np.array([ x1, -0.25*mm/2, 20e-6 if FC else thickness]) 285 | if size == '0402': 286 | stop[1] = -0.25*mm 287 | for m in [np.array([1,-1,zm]), np.array([1,1,zm])]: 288 | Box(metal, priority, origin+orient(start*m), origin+orient(stop*m), padname = None) 289 | # resistor body 290 | start = np.array([-x1, -y2, 20e-6*zm]) 291 | stop = np.array([ x1, y2, (thickness - 20e-6)*zm]) 292 | body = Box(dielectric, priority+1, origin+orient(start), origin+orient(stop), padname=None) 293 | # resistor element 294 | zoff = 0.0 if element_down else thickness - 20e-6 295 | start = np.array([-x2, -y3, zoff*zm]) 296 | stop = np.array([ x2, y3, (20e-6+zoff)*zm]) 297 | Box(element, priority+1, origin+orient(start), origin+orient(stop), padname = None) 298 | 299 | class Port(Object): 300 | def __init__(self, em, start, stop, direction, z, padname = None, layer = 'F.Cu', mirror = ''): 301 | self.em = em 302 | mirror = [-1 if 'x' in mirror else 1, -1 if 'y' in mirror else 1, 1] 303 | self.start = np.array(start)*mirror 304 | self.stop = np.array(stop)*mirror 305 | self.direction = direction 306 | self.z = z 307 | self.padname = padname 308 | self.layer = layer 309 | self.portnumber = len(em.ports) 310 | name = "p" + str(self.portnumber) 311 | em.objects[name] = self 312 | em.ports.append(self) 313 | def generate_octave(self): 314 | self.port = self.em.FDTD.AddLumpedPort( 315 | self.portnumber, 316 | R=self.z, 317 | start=self.start, 318 | stop=self.stop, 319 | p_dir=self.direction, 320 | excite=1.0 if self.em.excitation_port == self.portnumber else 0) 321 | def generate_kicad(self, g): 322 | if self.padname == None: 323 | return 324 | g.add_pad(self.padname, 325 | layer = self.layer, 326 | x = 500.0 * (self.start[0] + self.stop[0]), # mm 327 | y = 500.0 * (self.start[1] + self.stop[1]), # mm 328 | xsize = 1000.0 * abs(self.start[0] - self.stop[0]), # mm 329 | ysize = 1000.0 * abs(self.start[1] - self.stop[1])) 330 | 331 | class OpenEMS: 332 | def __init__(self, name, fmin=0, fmax=50e9, 333 | NrTS=1e6, 334 | EndCriteria=1e-6, 335 | #BC = {xmin xmax ymin ymax zmin zmax}; 336 | boundaries = ['PEC', 'PEC', 'PEC', 'PEC', 'PEC', 'PEC'], 337 | fsteps = 1601 338 | ): 339 | self.FDTD = openEMS(NrTS=NrTS, EndCriteria=EndCriteria) 340 | self.FDTD.SetGaussExcite((fmin+fmax)/2.0, (fmax-fmin)/2.0) 341 | self.FDTD.SetBoundaryCond(boundaries) 342 | self.CSX = ContinuousStructure() 343 | self.FDTD.SetCSX(self.CSX) 344 | self.mesh = self.CSX.GetGrid() 345 | self.mesh.SetDeltaUnit(1.0) # specify everything in m 346 | self.fmin = fmin 347 | self.fmax = fmax 348 | self.fsteps = fsteps 349 | self.objects = {} 350 | self.name = name 351 | self.ports = [] 352 | self.excitation_port = 0 353 | self.excite_ports = [1] 354 | self.via_offset_x = 0.0 355 | self.via_offset_y = 0.0 356 | self.xgrid = None # for plot 357 | self.ygrid = None 358 | self.legend_location = 2 # upper left 359 | self.options = '' 360 | self.name_count = 0 361 | self.resolution = 0.0001 362 | 363 | def AddPort(self, start, stop, direction, z): 364 | return Port(self, start, stop, direction, z) 365 | 366 | def get_name(self, name): 367 | if name: 368 | return name 369 | self.name_count += 1 370 | return "pad_{}".format(self.name_count) 371 | 372 | def write_kicad(self, fpname, mirror=""): 373 | g = kicadfpwriter.Generator(fpname) 374 | g.mirror = mirror 375 | for object in self.objects: 376 | g.drill = 0 377 | self.objects[object].generate_kicad(g) 378 | fp = g.finish() 379 | with open(self.name+".kicad_mod", "w") as f: 380 | f.write(fp) 381 | 382 | def run_openems(self, options='view solve', z=50, initialize=True, show_plot=True, numThreads=8): 383 | cwd = os.getcwd() 384 | basename = cwd + '/' + self.name 385 | simpath = r'/tmp/openems_data' + self.name.split("/")[-1] 386 | if not os.path.exists(simpath): 387 | os.mkdir(simpath) 388 | 389 | for object in self.objects: 390 | self.objects[object].generate_octave() 391 | 392 | import collections.abc 393 | if not isinstance(self.resolution, collections.abc.Sequence): 394 | self.resolution = np.ones(3) * self.resolution 395 | 396 | ratio = 1.5 397 | for i in range(3): 398 | if self.resolution[i] is not None: 399 | self.mesh.SmoothMeshLines('xyz'[i], self.resolution[i], ratio) 400 | 401 | if 'view' in options: 402 | CSX_file = simpath + '/csx.xml' 403 | self.CSX.Write2XML(CSX_file) 404 | os.system(r'AppCSXCAD "{}"'.format(CSX_file)) 405 | if 'solve' in options: 406 | self.FDTD.Run(simpath, verbose=3, cleanup=True, numThreads=numThreads) 407 | f = np.linspace(self.fmin, self.fmax, self.fsteps) 408 | for p in self.ports: 409 | p.port.CalcPort(simpath, f, ref_impedance = z) 410 | nports = len(self.ports) 411 | 412 | s = [] 413 | 414 | for p in range(nports): 415 | s.append(self.ports[p].port.uf_ref / self.ports[0].port.uf_inc) 416 | 417 | if nports < 1: 418 | return 419 | 420 | self.frequencies = f 421 | fig, ax = matplotlib.pyplot.subplots() 422 | s11 = s[0] 423 | if nports == 1: 424 | save_s1p(f, s11, basename+".s1p", z=z) 425 | if nports > 1: 426 | s21 = s[1] 427 | ax.plot(f/1e9, 20*np.log10(np.abs(s21)), label = 'dB(s21)') 428 | save_s2p_symmetric(f, s11, s21, basename+".s2p", z=z) 429 | ax.plot(f/1e9, 20*np.log10(np.abs(s11)), label = 'dB(s11)') 430 | if nports > 2: 431 | for i in range(2,nports): 432 | ax.plot(f/1e9, 20*np.log10(np.abs(s[i])), label = f'dB(s{i+1}1)') 433 | 434 | ax.set_xlabel('Frequency (GHz)') 435 | ax.set_ylabel('dB') 436 | if hasattr(self.xgrid, "__len__"): 437 | ax.set_xticks(self.xgrid) 438 | if hasattr(self.ygrid, "__len__"): 439 | ax.set_yticks(self.ygrid) 440 | ax.grid(True) 441 | fig.tight_layout() 442 | ax.legend(loc=self.legend_location) 443 | matplotlib.pyplot.savefig(basename+".png") 444 | matplotlib.pyplot.savefig(basename+".svg") 445 | matplotlib.pyplot.savefig(basename+".pdf") 446 | if show_plot: 447 | matplotlib.pyplot.show() 448 | return s 449 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.svg 2 | *.png 3 | *.s2p 4 | *.s1p 5 | *.pdf -------------------------------------------------------------------------------- /examples/LPF_DGS_bowtie_15.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import openems 4 | import openems.LPF_DGS_bowtie 5 | import numpy as np 6 | 7 | em = openems.OpenEMS('LPF_DGS_bowtie_15', EndCriteria = 1e-6, fmin = 0e6, fmax = 50e9, fsteps = 1601) 8 | em.resolution = [25e-6, 25e-6, 500e-6] 9 | 10 | fc = 6e9 11 | 12 | foil_thickness = 35e-6 13 | substrate_thickness = 100e-6 14 | 15 | z = np.zeros(6) 16 | z[1] = 1.6e-3 17 | z[2] = z[1] + foil_thickness 18 | z[3] = z[2] + substrate_thickness 19 | z[4] = z[3] + foil_thickness 20 | z[5] = z[4] + 1.6e-3 21 | 22 | openems.LPF_DGS_bowtie.generate( 23 | em, 24 | sub = openems.Dielectric(em, 'polyimide', eps_r=3.2, tand=0.002, fc=fc), 25 | mask = openems.Dielectric(em, 'soldermask', eps_r=3.3, tand=0.020, fc=fc), 26 | min_width = 152e-6, 27 | cutout_width = 1.0e-3, 28 | inductors = 0.15e-3 + 0.8*np.array([1.7e-3, 1.96e-3, 2e-3]), 29 | capacitors = np.array([0.47e-3, 0.77e-3, 0.81e-3, 0.82e-3]), 30 | z = z, 31 | port_length = 75e-6, 32 | ms_width = 195e-6, 33 | box_width = 2e-3) 34 | 35 | em.write_kicad(em.name) 36 | command = 'view solve' 37 | if len(sys.argv) > 1: 38 | command = sys.argv[1] 39 | em.run_openems(command) 40 | -------------------------------------------------------------------------------- /examples/LPF_DGS_bowtie_7.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import openems 4 | import openems.LPF_DGS_bowtie 5 | import numpy as np 6 | 7 | em = openems.OpenEMS('LPF_DGS_bowtie_7', EndCriteria = 1e-5, fmin = 0e6, fmax = 30e9, fsteps = 1601) 8 | em.resolution = [50e-6, 50e-6, 500e-6] 9 | 10 | fc = 6e9 11 | 12 | foil_thickness = 35e-6 13 | substrate_thickness = 100e-6 14 | 15 | z = np.zeros(6) 16 | z[1] = 1.6e-3 17 | z[2] = z[1] + foil_thickness 18 | z[3] = z[2] + substrate_thickness 19 | z[4] = z[3] + foil_thickness 20 | z[5] = z[4] + 1.6e-3 21 | 22 | openems.LPF_DGS_bowtie.generate( 23 | em, 24 | sub = openems.Dielectric(em, 'polyimide', eps_r=3.2, tand=0.002, fc=fc), 25 | mask = openems.Dielectric(em, 'soldermask', eps_r=3.3, tand=0.020, fc=fc), 26 | min_width = 152e-6, 27 | cutout_width = 1.0e-3, 28 | inductors = 0.15e-3 + 1.55*np.array([1.7e-3, 1.96e-3, 2e-3]), 29 | capacitors = 1.45*np.array([0.47e-3, 0.77e-3, 0.81e-3, 0.82e-3]), 30 | z = z, 31 | port_length = 75e-6, 32 | ms_width = 195e-6, 33 | box_width = 3e-3) 34 | 35 | em.write_kicad(em.name) 36 | command = 'view solve' 37 | if len(sys.argv) > 1: 38 | command = sys.argv[1] 39 | em.run_openems(command) 40 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | ## Vertical 2.92 mm connector (vert_connector_ms_oshpark.py) 4 | 5 | Hirose HK-R-SR2-1 2.92 mm connector, footprint is compatible with 6 | HRM(G)-300-468B-1 (SMA) and Rosenberger 03K721-40MS3 (3.5 7 | mm). Transition to microstrip on opposite side of an OSHPark 4 layer 8 | PCB. The [Kicad footprint file](vert_connector_ms_oshpark.kicad_mod) is 9 | manually edited to add mask clearance, copper clearance and mounting 10 | holes. See [Breakout PCBs]( https://gitlab.com/harmoninstruments/breakout-pcbs/tree/master) 11 | for usage in a project. 12 | 13 | ### Response 14 | ![response plot](images/vert_connector_ms_oshpark.png "response plot") 15 | 16 | ## Capacitor Ground Cutout (capacitor_ground_cutout.py) 17 | 18 | ### Usage 19 | ``` 20 | $ ./capacitor_ground_cutout.py 0201 view_solve 21 | ``` 22 | 23 | '0201' may be substituted for '0402' and refers to the inch capacitor size. 24 | 25 | 'view_solve' may be reduced to just 'view' or 'solve' to only view or only solve. 26 | -------------------------------------------------------------------------------- /examples/capacitor_ground_cutout.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import openems 4 | import numpy as np 5 | 6 | mm = 1e-3 # default unit is the meter 7 | em = openems.OpenEMS(sys.argv[1], EndCriteria = 1e-6, fmin = 0e6, fmax = 60e9) 8 | em.fsteps = 801 9 | copper = openems.Metal(em, 'copper') 10 | sub = openems.Dielectric(em, 'substrate', eps_r=3.2) 11 | mask = openems.Dielectric(em, 'mask', eps_r=3.3) 12 | air = openems.Dielectric(em, 'air', eps_r=1.0006) 13 | 14 | foil_thickness = 0.035*mm 15 | port_length = 0.05*mm 16 | box_length = 5*mm 17 | box_width = 1.5*mm 18 | ms_width = 0.190*mm 19 | box_top = 1.5*mm 20 | 21 | # dimensions Z 22 | substrate_top = 0.102*mm 23 | foil_top = substrate_top + foil_thickness 24 | em.resolution = 50e-6 25 | 26 | em.mesh.AddLine('z', foil_top + box_top) 27 | em.mesh.AddLine('z', -0.5*mm) 28 | 29 | # substrate 30 | start = np.array([-0.5*box_length, 0.5*box_width, 0]) 31 | stop = np.array([0.5*box_length, -0.5*box_width, substrate_top]) 32 | sub.AddBox(start, stop, priority=2) 33 | start[2] = stop[2] + 25.4e-6 34 | mask.AddBox(start, stop, priority=0) 35 | 36 | # bottom foil 37 | start = np.array([-0.5*box_length, 0.5*box_width, 0]) 38 | stop = np.array([0.5*box_length, -0.5*box_width, -foil_thickness]) 39 | copper.AddBox(start, stop, priority=2) 40 | 41 | if sys.argv[1] == '0201': 42 | pad_y = 0.5 * 0.3*mm 43 | pad_x1 = 0.125*mm 44 | pad_x2 = 0.35*mm 45 | body_y = 0.15*mm 46 | body_x = 0.3*mm 47 | body_z = 0.3*mm 48 | cutout_x = 0.5 * 0.6*mm 49 | cutout_y = 0.5 * 0.42*mm 50 | 51 | elif sys.argv[1] == '0402': 52 | pad_y = 0.25*mm 53 | pad_x1 = 0.25*mm 54 | pad_x2 = 0.65*mm 55 | body_y = 0.25*mm 56 | body_x = 0.5*mm 57 | body_z = 0.5*mm 58 | cutout_x = 0.5 * 1.6*mm 59 | cutout_y = 0.5 * 0.6*mm 60 | else: 61 | exit() 62 | 63 | for xm in [-1,1]: 64 | # line 65 | start = np.array([xm*pad_x1, 0.5*ms_width, substrate_top]) 66 | stop = np.array([xm*0.5*box_length-port_length, -0.5*ms_width, foil_top]) 67 | copper.AddBox(start, stop, priority=9) 68 | 69 | # 0201 pads 70 | start = np.array([xm*pad_x1, pad_y, substrate_top]) 71 | stop = np.array([xm*pad_x2, -pad_y, foil_top]) 72 | copper.AddBox(start, stop, priority=9) 73 | 74 | # 0201 body 75 | start = np.array([-body_x, -body_y, foil_top + body_z]) 76 | stop = np.array([ body_x, body_y, foil_top]) 77 | copper.AddBox(start, stop, padname = '1', priority=9) 78 | 79 | # ground plane cutout 80 | start = np.array([-cutout_x, -cutout_y, 0]) 81 | stop = np.array([ cutout_x, cutout_y, -foil_thickness]) 82 | air.AddBox(start, stop, priority=9) 83 | 84 | # port (ms) 85 | start = [-0.5*box_length, ms_width/2.0, substrate_top] 86 | stop = [-0.5*box_length + port_length, ms_width/-2.0, foil_top] 87 | openems.Port(em, start, stop, direction='x', z=50) 88 | 89 | # port (ms) 90 | start = [0.5*box_length, ms_width/2.0, substrate_top] 91 | stop = [0.5*box_length - port_length, ms_width/-2.0, foil_top] 92 | openems.Port(em, start, stop, direction='x', z=50) 93 | 94 | command = 'view solve' 95 | if len(sys.argv) > 2: 96 | command = sys.argv[2] 97 | em.run_openems(command) 98 | -------------------------------------------------------------------------------- /examples/field_replaceable_oshpark_flex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # 0.020" pin to OSHPark flex 3 | import sys 4 | from scipy.constants import pi, c, epsilon_0, mu_0, mil, inch 5 | mm = 0.001 6 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric, Polygon, arc 7 | import numpy as np 8 | use_PML_coax = True 9 | 10 | em = OpenEMS( 11 | 'field_replaceable_to_oshpark_flex', 12 | EndCriteria = 1e-6, 13 | fmin = 0e6, 14 | fmax = 60e9, 15 | fsteps = 1001, 16 | boundaries = ['PEC', 'PML_16' if use_PML_coax else 'PEC', 'PEC', 'PEC', 'PEC', 'PEC'], 17 | ) 18 | copper = Metal(em, 'copper') 19 | copper_shield = Metal(em, 'copper_shield') 20 | copper_ms = Metal(em, 'copper_ms') 21 | sub = Dielectric(em, 'ro4350b', eps_r=3.2, tand=0.0035, fc=em.fmax) 22 | teflon = Dielectric(em, 'teflon', eps_r=2.1, tand=0.0002, fc=em.fmax) 23 | 24 | foil_thickness = 0.035*mm 25 | substrate_thickness = 4*mil 26 | port_length = 0.15*mm 27 | box_length = 3*mm 28 | box_width = 2*mm 29 | ms_width = 0.21*mm 30 | board_gap = 0.1*mm 31 | 32 | # coax 33 | pin_radius = 254e-6 34 | dielectric_radius = 0.5*67*mil 35 | coax_port_length = 0.2*mm 36 | 37 | box_height = dielectric_radius 38 | 39 | # dimensions Z 40 | foil_top = -(pin_radius) 41 | substrate_top = foil_top - foil_thickness 42 | substrate_bottom = substrate_top - substrate_thickness 43 | 44 | em.resolution = [50e-6, 15e-6, 15e-6] 45 | 46 | # substrate 47 | start = np.array([-0.5*box_length, 0.5*box_width, substrate_bottom]) 48 | stop = np.array([0.0, -0.5*box_width, substrate_top]) 49 | Box(sub, 1, start, stop); 50 | 51 | # line 52 | port_length = 0.065*mm 53 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, substrate_top]) 54 | stop = np.array([0, -0.5*ms_width, foil_top]) 55 | Box(copper_ms, 1, start, stop, padname = None) 56 | 57 | # port (ms) 58 | stop[0] = -0.5*box_length 59 | Port(em, start, stop, direction='x', z=50) 60 | 61 | # pad 62 | ypad = 0.38e-3 63 | xpad = -0.62e-3 64 | xpad2 = -0.8e-3 65 | 66 | points = np.array([ 67 | [0, ypad], [xpad, ypad], [xpad2, 0], [xpad, -ypad], [0, -ypad]]) 68 | Polygon(copper, points, [substrate_top, foil_top], is_custom_pad=True, pad_name='1', x=0.5*xpad, y=0) 69 | 70 | # solder 71 | start = np.array([-0.5*mm, pin_radius, 0]) 72 | stop = np.array([0, -pin_radius, foil_top]) 73 | Box(Metal(em, 'solder'), 1, start, stop, padname = None) 74 | 75 | z1 = substrate_bottom - foil_thickness 76 | z2 = -(dielectric_radius + 1e-4) 77 | 78 | gp = Metal(em, 'groundplane') 79 | y1 = ypad+20e-6 80 | x1 = xpad2-00e-6 81 | points = np.array([ 82 | [x1,y1], [0, y1], [0, 0.5*box_width], [-0.5*box_length, 0.5*box_width]]) 83 | Polygon( 84 | copper, 85 | np.concatenate((points, points[::-1]*[1,-1])), 86 | [z1, substrate_bottom], is_custom_pad=True, pcb_layer='B.Cu', pad_name='2', x=x1-0.3e-3, y=0) 87 | 88 | angle = np.arccos(-z1/dielectric_radius) 89 | # in yz plane 90 | points = np.array([ 91 | [0.5*box_width,z1], 92 | [0.5*box_width,z2], 93 | [-0.5*box_width,z2], 94 | [-0.5*box_width,z1]]) 95 | pin_arc = arc(0,0, dielectric_radius, 1.5*pi-angle, 1.5*pi+angle) 96 | Polygon(copper_shield, 97 | points = np.concatenate((points, pin_arc)), 98 | elevation = [board_gap, -0.5*box_length], 99 | normal_direction = 'x', 100 | pcb_layer=None 101 | ) 102 | 103 | # shield 104 | start = np.array([0.5*box_length, 0.5*box_width, dielectric_radius]) 105 | stop = np.array([board_gap, -0.5*box_width, -dielectric_radius]) 106 | Box(copper_shield, 1, start, stop, padname = None) 107 | 108 | # dielectric 109 | start = np.array([0.5*box_length, 0, 0]) 110 | stop = np.array([board_gap, 0, 0]) 111 | Cylinder(teflon, 2, start, stop, dielectric_radius) 112 | 113 | # pin 114 | start = np.array([0.5*box_length - coax_port_length, 0, 0]) 115 | if use_PML_coax: 116 | start = np.array([0.5*box_length, 0, 0]) 117 | stop = np.array([-0.5*mm, 0, 0]) 118 | Cylinder(copper, 3, start, stop, pin_radius) 119 | 120 | # port (coax) 121 | start = [0.5*box_length, -0.5*coax_port_length, -0.5*coax_port_length] 122 | stop = [0.5*box_length - coax_port_length, 0.5*coax_port_length, 0.5*coax_port_length] 123 | if not use_PML_coax: 124 | Port(em, start, stop, direction='x', z=50) 125 | 126 | em.write_kicad(em.name) 127 | command = 'view solve' 128 | if len(sys.argv) > 1: 129 | command = sys.argv[1] 130 | print(command) 131 | em.run_openems(command) 132 | -------------------------------------------------------------------------------- /examples/idbpf_tap_oshpark/README.md: -------------------------------------------------------------------------------- 1 | # Tapped interdigital bandpasses for OSHPark 4 layer 2 | 3 | These use the first inner layer as ground. 4 | 5 | The first argument is the center frequency in GHz. Valid values are 3, 4, 5, 6. The 2nd argument can be view, solve or be left out to do both. 6 | 7 | ```bash 8 | ./filter.py 4 solve 9 | ``` -------------------------------------------------------------------------------- /examples/idbpf_tap_oshpark/filter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from scipy.constants import pi, c, epsilon_0, mu_0, mil, inch 4 | mm = 0.001 5 | import openems 6 | import openems.idbpf_tapped 7 | import numpy as np 8 | 9 | idx = int(sys.argv[1]) 10 | name = 'idbpf_{}'.format(idx) 11 | fmax = [0,0,0,8e9,8e9,10e9,12e9][idx] 12 | em = openems.OpenEMS(name, fmin=0, fmax=fmax, fsteps=400, EndCriteria=1e-5) 13 | fc = [0, 0, 0, 3e9, 4e9, 5e9, 6e9][idx] 14 | pec = openems.Metal(em, 'pec') 15 | sub = openems.Dielectric(em, 'sub', eps_r=3.3, tand = 0.012, fc=fc) 16 | 17 | foil_thickness = 0.04*mm 18 | substrate_thickness = 0.18*mm 19 | ms_air_above = 1.0*mm 20 | via = 0.4*mm 21 | via_pad = 0.8*mm 22 | 23 | # dimensions Z 24 | substrate_bottom = 0.0 25 | substrate_top = substrate_bottom + substrate_thickness 26 | foil_top = substrate_top + foil_thickness 27 | em.mesh.AddLine('z', foil_top + ms_air_above) 28 | 29 | em.resolution = [0.1*mm, 0.04*mm, 0.5*mm] 30 | 31 | rl = [0,0,0,14.4*mm,10.8*mm,8.5*mm,7.1*mm][idx] 32 | rl = [rl]*6 33 | rl[0] += [0,0,0,0.95*mm,0.8*mm,0.6*mm,0.6*mm][idx] 34 | 35 | etch = 0.0 # etch error 36 | openems.idbpf_tapped.idbpf( 37 | em, 38 | sub = sub, 39 | tapoffset=[0,0,0,5.1*mm,3.8*mm,3.05*mm,2.5*mm][idx], 40 | rl = rl, 41 | rw = np.array([0.36*mm]*6) - etch, 42 | space = np.array([0.15*mm, 0.19*mm]) + etch, 43 | via_padradius = 0.5*via_pad, 44 | via_radius = 0.5*via, 45 | feedwidth = 0.36*mm, 46 | portlength = 0.3*mm, 47 | z = [substrate_bottom, substrate_top, foil_top], 48 | endmetal = False 49 | ) 50 | 51 | em.write_kicad(name) 52 | 53 | command = 'view solve' 54 | if len(sys.argv) > 2: 55 | command = sys.argv[2] 56 | em.xgrid = np.linspace(0, fmax/1e9, 1+int(fmax/1e9)) 57 | em.ygrid = np.linspace(-50.0, 0, 11) 58 | em.run_openems(command) 59 | -------------------------------------------------------------------------------- /examples/images/vert_connector_ms_oshpark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dlharmon/pyopenems/5998cd92fa048b085893cb2977af772264a78c7b/examples/images/vert_connector_ms_oshpark.png -------------------------------------------------------------------------------- /examples/microstrip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | mm = 0.001 4 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric 5 | import numpy as np 6 | 7 | em = OpenEMS('microstrip', EndCriteria = 1e-5, fmin = 0e6, fmax = 60e9, fsteps = 1601) 8 | copper = Metal(em, 'copper') 9 | sub = Dielectric(em, 'substrate', eps_r=3.2) 10 | 11 | foil_thickness = 0.036*mm 12 | box_length = 5*mm 13 | box_width = 2.5*mm 14 | box_top = 1.5*mm 15 | 16 | port_length = 0.1*mm 17 | substrate_top = 0.166*mm 18 | ms_width = 0.35*mm 19 | 20 | # oshpark flex 21 | port_length = 0.065*mm 22 | substrate_top = 4*25.4e-6 23 | ms_width = 0.21*mm 24 | box_width = 1.5*mm 25 | box_top = 1*mm 26 | 27 | foil_top = substrate_top + foil_thickness 28 | 29 | em.resolution = 10e-6 30 | 31 | em.mesh.AddLine('z', foil_top + box_top) 32 | 33 | # substrate 34 | start = np.array([-0.5*box_length, 0.5*box_width, 0]) 35 | stop = np.array([0.5*box_length, -0.5*box_width, substrate_top]) 36 | sub.AddBox(start, stop, priority=2) 37 | 38 | # line 39 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, substrate_top]) 40 | stop = np.array([0.5*box_length-port_length, -0.5*ms_width, foil_top]) 41 | copper.AddBox(start, stop, padname = '1', priority=9) 42 | 43 | # port (ms) 44 | start = [-0.5*box_length, ms_width/2.0, substrate_top] 45 | stop = [-0.5*box_length + port_length, ms_width/-2.0, foil_top] 46 | Port(em, start, stop, direction='x', z=50) 47 | 48 | # port (ms) 49 | start = [0.5*box_length, ms_width/2.0, substrate_top] 50 | stop = [0.5*box_length - port_length, ms_width/-2.0, foil_top] 51 | Port(em, start, stop, direction='x', z=50) 52 | 53 | command = 'view solve' 54 | if len(sys.argv) > 1: 55 | command = sys.argv[1] 56 | em.run_openems(command) 57 | -------------------------------------------------------------------------------- /examples/microstrip_PML.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | mm = 0.001 4 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric 5 | import numpy as np 6 | import argparse 7 | 8 | parser = argparse.ArgumentParser() 9 | #parser.add_argument('--command', type=str, help="text above graph", default="S Parameters") 10 | parser.add_argument('--view', action='store_true', help="view CSXCAD") 11 | parser.add_argument('--solve', action='store_true', help="run solver") 12 | parser.add_argument('--Er', type=float, help="dielectric", default=4.3) 13 | parser.add_argument('--Fc', type=float, help="center frequency", default=12e9) 14 | parser.add_argument('--Fmax', type=float, help="stop frequency", default=60e9) 15 | args = parser.parse_args() 16 | 17 | em = OpenEMS('microstrip', EndCriteria = 1e-5, fmin = 0e6, fmax = args.Fmax, fsteps = 1601, boundaries = ['PEC', 'PML_12', 'PEC', 'PEC', 'PEC', 'PEC']) 18 | copper = Metal(em, 'copper') 19 | sub = Dielectric(em, 'substrate', eps_r=args.Er, tand=0.00, fc=args.Fc) 20 | 21 | foil_thickness = 0.036*mm 22 | box_length = 10*mm 23 | box_width = 2.5*mm 24 | box_top = 1.5*mm 25 | 26 | port_length = 0.065*mm 27 | substrate_top = 100e-6 28 | ms_width = 0.156*mm 29 | box_width = 1*mm 30 | box_top = 0.5*mm 31 | 32 | foil_top = substrate_top + foil_thickness 33 | 34 | em.resolution = 25e-6 35 | 36 | em.mesh.AddLine('z', foil_top + box_top) 37 | 38 | # substrate 39 | start = np.array([-0.5*box_length, 0.5*box_width, 0]) 40 | stop = np.array([0.5*box_length, -0.5*box_width, substrate_top]) 41 | sub.AddBox(start, stop, priority=2) 42 | 43 | # line 44 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, substrate_top]) 45 | stop = np.array([0.5*box_length, -0.5*ms_width, foil_top]) 46 | copper.AddBox(start, stop, padname = '1', priority=9) 47 | 48 | # port (ms) 49 | start = [-0.5*box_length, ms_width/2.0, substrate_top] 50 | stop = [-0.5*box_length + port_length, ms_width/-2.0, foil_top] 51 | Port(em, start, stop, direction='x', z=50) 52 | 53 | command = '' 54 | if args.view: 55 | command += 'view ' 56 | if args.solve: 57 | command += 'solve ' 58 | s = em.run_openems(command) 59 | 60 | if s is not None: 61 | s_dc = s[0][0] 62 | z_dc = 50.0 * (1+s_dc)/(1-s_dc) 63 | print(f"Z DC = {z_dc} ohms") 64 | print(s[0][0]) 65 | -------------------------------------------------------------------------------- /examples/miter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from scipy.constants import pi, c, epsilon_0, mu_0, mil, inch 4 | mm = 0.001 5 | import openems 6 | import openems.miter 7 | import numpy as np 8 | 9 | em = openems.OpenEMS('miter_6.6') 10 | em.end_criteria = 1e-6 11 | em.fmin = 0 12 | em.fmax = 40e9 13 | em.fsteps = 1601 14 | fc = 40e9 15 | pec = openems.Metal(em, 'pec') 16 | sub = openems.Dielectric(em, 'ro4350b', eps_r=3.66, tand=0.0035, fc=fc) 17 | 18 | foil_thickness = 0.033*mm 19 | substrate_thickness = 6.6*mil 20 | ms_air_above = 1.0*mm 21 | z = [0.0, substrate_thickness, substrate_thickness + foil_thickness] 22 | em.mesh.AddLine('z', z[2] + ms_air_above) 23 | 24 | em.resolution = 50e-6 25 | 26 | openems.miter.generate( 27 | em, 28 | pec, 29 | sub, 30 | miter = 0.35 * mm, 31 | z = z, 32 | port_length = 0.1 * mm, 33 | ms_width = 0.3 *mm, 34 | box_size = 1.2*mm) 35 | 36 | em.write_kicad(em.name, mirror = '') 37 | em.write_kicad(em.name+'_m', mirror = 'y') 38 | command = 'view solve' 39 | if len(sys.argv) > 1: 40 | command = sys.argv[1] 41 | print(command) 42 | em.run_openems(command) 43 | -------------------------------------------------------------------------------- /examples/openems: -------------------------------------------------------------------------------- 1 | ../ -------------------------------------------------------------------------------- /examples/semi_rigid_047_oshpark_flex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # UT-047 to OSHPark flex 3 | import sys 4 | from scipy.constants import pi, c, mil 5 | mm = 0.001 6 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric, Polygon, arc 7 | import numpy as np 8 | 9 | em = OpenEMS( 10 | 'semi_rigid_047_oshpark_flex', 11 | EndCriteria = 1e-6, 12 | fmin = 0e6, 13 | fmax = 60e9, 14 | fsteps = 1001, 15 | boundaries = ['PEC', 'PML_16', 'PEC', 'PEC', 'PEC', 'PEC'], 16 | ) 17 | copper = Metal(em, 'copper') 18 | copper_shield = Metal(em, 'copper_shield') 19 | copper_ms = Metal(em, 'copper_ms') 20 | sub = Dielectric(em, 'ro4350b', eps_r=3.2, tand=0.0035, fc=em.fmax) 21 | teflon = Dielectric(em, 'teflon', eps_r=2.1, tand=0.0002, fc=em.fmax) 22 | 23 | foil_thickness = 0.05*mm 24 | substrate_thickness = 4*mil 25 | box_length = 4*mm 26 | box_width = 2.2*mm 27 | ms_width = 0.19*mm 28 | board_gap = 0 29 | 30 | # coax 31 | pin_radius = 0.5*11.3*mil 32 | dielectric_radius = 0.5*37*mil 33 | 34 | box_height = dielectric_radius 35 | 36 | # dimensions Z 37 | foil_top = -(pin_radius) 38 | substrate_top = foil_top - foil_thickness 39 | substrate_bottom = substrate_top - substrate_thickness 40 | 41 | em.resolution = [50e-6, 25e-6, 25e-6] 42 | 43 | # substrate 44 | start = np.array([-0.5*box_length, 0.5*box_width, substrate_bottom]) 45 | stop = np.array([0.0, -0.5*box_width, substrate_top]) 46 | Box(sub, 1, start, stop); 47 | 48 | # line 49 | port_length = 0.065*mm 50 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, substrate_top]) 51 | stop = np.array([0, -0.5*ms_width, foil_top]) 52 | Box(copper_ms, 1, start, stop, padname = None) 53 | 54 | # port (ms) 55 | stop[0] = -0.5*box_length 56 | Port(em, start, stop, direction='x', z=50) 57 | 58 | # pad 59 | ypad = 0.3e-3 60 | xpad = -0.62e-3 61 | xpad2 = -0.8e-3 62 | xpad1 = -0.1e-3 63 | points = np.array([ 64 | [0,0], [xpad1, ypad], [xpad, ypad], [xpad2, 0], [xpad, -ypad], [xpad1, -ypad]]) 65 | Polygon(copper, points, [substrate_top, foil_top], is_custom_pad=True, pad_name='1', x=0.5*xpad, y=0) 66 | 67 | # solder 68 | start = np.array([-0.5*mm, pin_radius, 0]) 69 | stop = np.array([0, -pin_radius, foil_top]) 70 | Box(Metal(em, 'solder'), 1, start, stop, padname = None) 71 | 72 | z1 = substrate_bottom - foil_thickness 73 | z2 = -(dielectric_radius + 1e-4) 74 | 75 | gp = Metal(em, 'groundplane') 76 | y1 = ypad-20e-6 77 | x1 = xpad2-00e-6 78 | points = np.array([ 79 | [x1,y1], [0, y1], [0, 0.5*box_width], [-0.5*box_length, 0.5*box_width]]) 80 | Polygon( 81 | copper, 82 | np.concatenate((points, points[::-1]*[1,-1])), 83 | [z1, substrate_bottom], is_custom_pad=True, pcb_layer='B.Cu', pad_name='2', x=x1-0.3e-3, y=0) 84 | 85 | angle = np.arccos(-z1/dielectric_radius) 86 | 87 | # shield 88 | start = np.array([0.5*box_length, 0.5*box_width, 1*mm]) 89 | stop = np.array([board_gap, -0.5*box_width, -1*mm]) 90 | Box(copper_shield, 1, start, stop, padname = None) 91 | 92 | # dielectric 93 | start = np.array([0.5*box_length, 0, 0]) 94 | stop = np.array([board_gap, 0, 0]) 95 | Cylinder(teflon, 2, start, stop, dielectric_radius) 96 | 97 | # pin 98 | start = np.array([0.5*box_length, 0, 0]) 99 | stop = np.array([-0.5*mm, 0, 0]) 100 | Cylinder(copper, 3, start, stop, pin_radius) 101 | 102 | em.write_kicad(em.name) 103 | command = 'view solve' 104 | if len(sys.argv) > 1: 105 | command = sys.argv[1] 106 | print(command) 107 | em.run_openems(command) 108 | -------------------------------------------------------------------------------- /examples/semi_rigid_086_oshpark_flex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # RG405 to OSHPark flex 3 | import sys 4 | from scipy.constants import pi, c, mil, inch 5 | mm = 0.001 6 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric, Polygon, arc 7 | import numpy as np 8 | 9 | em = OpenEMS( 10 | 'semi_rigid_086_oshpark_flex', 11 | EndCriteria = 1e-6, 12 | fmin = 0e6, 13 | fmax = 60e9, 14 | fsteps = 1001, 15 | boundaries = ['PEC', 'PML_16', 'PEC', 'PEC', 'PEC', 'PEC'], 16 | ) 17 | copper = Metal(em, 'copper') 18 | copper_shield = Metal(em, 'copper_shield') 19 | copper_ms = Metal(em, 'copper_ms') 20 | sub = Dielectric(em, 'ro4350b', eps_r=3.2, tand=0.0035, fc=em.fmax) 21 | teflon = Dielectric(em, 'teflon', eps_r=2.1, tand=0.0002, fc=em.fmax) 22 | 23 | foil_thickness = 0.05*mm 24 | substrate_thickness = 4*mil 25 | box_length = 4*mm 26 | box_width = 2.2*mm 27 | ms_width = 0.19*mm 28 | board_gap = 0 29 | 30 | # coax 31 | pin_radius = 254e-6 32 | dielectric_radius = 0.5*67*mil 33 | 34 | box_height = dielectric_radius 35 | 36 | # dimensions Z 37 | foil_top = -(pin_radius) 38 | substrate_top = foil_top - foil_thickness 39 | substrate_bottom = substrate_top - substrate_thickness 40 | 41 | em.resolution = [50e-6, 25e-6, 25e-6] 42 | 43 | # substrate 44 | start = np.array([-0.5*box_length, 0.5*box_width, substrate_bottom]) 45 | stop = np.array([0.0, -0.5*box_width, substrate_top]) 46 | Box(sub, 1, start, stop); 47 | 48 | # line 49 | port_length = 0.065*mm 50 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, substrate_top]) 51 | stop = np.array([0, -0.5*ms_width, foil_top]) 52 | Box(copper_ms, 1, start, stop, padname = None) 53 | 54 | # port (ms) 55 | stop[0] = -0.5*box_length 56 | Port(em, start, stop, direction='x', z=50) 57 | 58 | # pad 59 | ypad = 0.4e-3 60 | xpad = -0.62e-3 61 | xpad2 = -0.8e-3 62 | xpad1 = -0.05e-3 63 | points = np.array([ 64 | [0,0], [xpad1, ypad], [xpad, ypad], [xpad2, 0], [xpad, -ypad], [xpad1, -ypad]]) 65 | Polygon(copper, points, [substrate_top, foil_top], is_custom_pad=True, pad_name='1', x=0.5*xpad, y=0) 66 | 67 | # solder 68 | start = np.array([-0.5*mm, pin_radius, 0]) 69 | stop = np.array([0, -pin_radius, foil_top]) 70 | Box(Metal(em, 'solder'), 1, start, stop, padname = None) 71 | 72 | z1 = substrate_bottom - foil_thickness 73 | z2 = -(dielectric_radius + 1e-4) 74 | 75 | gp = Metal(em, 'groundplane') 76 | y1 = ypad+30e-6 77 | x1 = xpad2-00e-6 78 | points = np.array([ 79 | [x1,y1], [0, y1], [0, 0.5*box_width], [-0.5*box_length, 0.5*box_width]]) 80 | Polygon( 81 | copper, 82 | np.concatenate((points, points[::-1]*[1,-1])), 83 | [z1, substrate_bottom], is_custom_pad=True, pcb_layer='B.Cu', pad_name='2', x=x1-0.3e-3, y=0) 84 | 85 | angle = np.arccos(-z1/dielectric_radius) 86 | 87 | # shield 88 | start = np.array([0.5*box_length, 0.5*box_width, 1.5*mm]) 89 | stop = np.array([board_gap, -0.5*box_width, -2*mm]) 90 | Box(copper_shield, 1, start, stop, padname = None) 91 | 92 | # dielectric 93 | start = np.array([0.5*box_length, 0, 0]) 94 | stop = np.array([board_gap, 0, 0]) 95 | Cylinder(teflon, 2, start, stop, dielectric_radius) 96 | 97 | # pin 98 | start = np.array([0.5*box_length, 0, 0]) 99 | stop = np.array([-0.5*mm, 0, 0]) 100 | Cylinder(copper, 3, start, stop, pin_radius) 101 | 102 | em.write_kicad(em.name) 103 | command = 'view solve' 104 | if len(sys.argv) > 1: 105 | command = sys.argv[1] 106 | print(command) 107 | em.run_openems(command) 108 | -------------------------------------------------------------------------------- /examples/sma_el.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Common SMA edge launch to OSHPark 4 layer 3 | import sys 4 | from scipy.constants import mil 5 | import openems 6 | import numpy as np 7 | 8 | em = openems.OpenEMS('sma_el', EndCriteria = 1e-6, fmin = 0, fmax = 20e9, fsteps = 1001) 9 | em.resolution = 8e-5 10 | copper = openems.Metal(em, 'copper') 11 | copper_shield = openems.Metal(em, 'copper_shield') 12 | sub = openems.Dielectric(em, 'ro4350b', eps_r=3.66, tand=0.0035, fc=em.fmax) 13 | teflon = openems.Dielectric(em, 'teflon', eps_r=2.1, tand=0.0002, fc=em.fmax) 14 | 15 | foil_thickness = 35e-6 16 | substrate_thickness = 62*mil-7*mil 17 | port_length = 300e-6 18 | box_length = 10e-3 19 | box_width = 6e-3 20 | box_height = 6e-3 21 | ms_width = 1.0e-3 22 | board_gap = 0.0 23 | 24 | # coax 25 | coax_scale = 2.0 26 | pin_diameter = 0.5e-3*coax_scale 27 | dielectric_diameter = 1.67e-3*coax_scale 28 | coax_port_length = 0.2e-3*coax_scale 29 | 30 | # dimensions Z 31 | foil_top = -0.5*pin_diameter 32 | substrate_top = foil_top - foil_thickness 33 | substrate_bottom = substrate_top - substrate_thickness 34 | 35 | # substrate 36 | start = np.array([-0.5*box_length, 0.5*box_width, substrate_bottom]) 37 | stop = np.array([0.0, -0.5*box_width, substrate_top]) 38 | openems.Box(sub, 1, start, stop); 39 | 40 | # line 41 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, substrate_top]) 42 | stop = np.array([0, -0.5*ms_width, foil_top]) 43 | openems.Box(copper, 1, start, stop, padname = '1') 44 | 45 | # solder 46 | start = np.array([-0.5e-3, 0.5*pin_diameter, foil_top+0.5*pin_diameter]) 47 | stop = np.array([0, -0.5*pin_diameter, foil_top]) 48 | openems.Box(copper, 1, start, stop, padname = None) 49 | 50 | # ground via 51 | start = np.array([-0.5*box_length, -0.5*ms_width-0.2e-3, foil_top]) 52 | stop = np.array([0.0, -0.5*box_width, substrate_top]) 53 | for m in [[1,1,1], [1,-1,1]]: 54 | openems.Box(copper, 10, start*m, stop*m, padname = '2') 55 | 56 | # ground via 57 | start = np.array([-0.5*box_length, -0.5*ms_width-0.3e-3, substrate_top]) 58 | stop = np.array([0.0, -0.5*box_width, substrate_bottom]) 59 | for m in [[1,1,1], [1,-1,1]]: 60 | openems.Box(copper, 10, m*start, m*stop, padname = None) 61 | 62 | # ground 63 | start = np.array([-0.5*box_length, 0.5*box_width, substrate_bottom]) 64 | stop = np.array([board_gap, -0.5*box_width, -0.5*box_height]) 65 | openems.Box(copper, 10, start, stop, padname = None) 66 | 67 | # port (ms) 68 | start = [-0.5*box_length, 0.5*ms_width, substrate_top] 69 | stop = [-0.5*box_length + port_length, -0.5*ms_width, foil_top] 70 | openems.Port(em, start, stop, direction='x', z=50) 71 | 72 | # shield 73 | start = np.array([0.5*box_length, 0.5*box_width, 0.5*box_height]) 74 | stop = np.array([board_gap, -0.5*box_width, -0.5*box_height]) 75 | openems.Box(copper_shield, 1, start, stop, padname = None) 76 | 77 | # dielectric 78 | start = np.array([0.5*box_length, 0, 0]) 79 | stop = np.array([board_gap, 0, 0]) 80 | openems.Cylinder(teflon, 2, start, stop, 0.5*dielectric_diameter) 81 | 82 | # pin 83 | start = np.array([0.5*box_length - coax_port_length, 0, 0]) 84 | stop = np.array([-0.5e-3, 0, 0]) 85 | openems.Cylinder(copper, 3, start, stop, 0.5*pin_diameter) 86 | 87 | # port (coax) 88 | start = [0.5*box_length, -0.25*coax_port_length, -0.25*coax_port_length] 89 | stop = [0.5*box_length - coax_port_length, 0.25*coax_port_length, 0.25*coax_port_length] 90 | openems.Port(em, start, stop, direction='x', z=50) 91 | 92 | em.write_kicad(em.name) 93 | command = 'view solve' 94 | if len(sys.argv) > 1: 95 | command = sys.argv[1] 96 | em.run_openems(command) 97 | -------------------------------------------------------------------------------- /examples/sma_th.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from scipy.constants import mil 4 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric 5 | import numpy as np 6 | 7 | em = OpenEMS('sma_th', EndCriteria = 1e-6, fmin = 0, fmax = 20e9, fsteps = 1601) 8 | em.resolution = 8e-5 9 | copper = Metal(em, 'copper') 10 | copper_shield = Metal(em, 'copper_shield') 11 | ro4350b = Dielectric(em, 'ro4350b', eps_r=3.66, tand=0.0035, fc=em.fmax) 12 | teflon = Dielectric(em, 'teflon', eps_r=2.1, tand=0.0002, fc=em.fmax) 13 | 14 | foil_thickness = 0.035e-3 15 | substrate_thickness = 62*mil 16 | port_length = 1.0e-3 17 | box_length = 6e-3 18 | box_width = 6e-3 19 | box_height = 7e-3 20 | ms_width = 2.5e-3 21 | 22 | # coax 23 | pin_diameter = 1.27e-3 24 | dielectric_diameter = 4.2e-3 25 | coax_port_length = 0.4e-3 26 | 27 | # dimensions Z 28 | foil_top = -0.5*pin_diameter 29 | substrate_top = foil_top - foil_thickness 30 | substrate_bottom = substrate_top - substrate_thickness 31 | 32 | em.mesh.AddLine('z', 0.5*box_height) 33 | 34 | # substrate 35 | Box(ro4350b, 1, 36 | [-0.5*box_length, 0.5*box_width, substrate_bottom], 37 | [0.5*box_length, -0.5*box_width, substrate_top] 38 | ) 39 | 40 | # line 41 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, substrate_top]) 42 | stop = np.array([0, -0.5*ms_width, foil_top]) 43 | Box(copper, 1, start, stop, padname = '1') 44 | 45 | # via top 46 | Cylinder(copper, 2, [0, 0, substrate_top], [0, 0, foil_top], 0.5*ms_width) 47 | 48 | # ground top 49 | start = np.array([-0.5*box_length, -0.5*ms_width-0.5e-3, foil_top]) 50 | stop = np.array([0.5*box_length, -0.5*box_width, substrate_top]) 51 | for m in ['', 'y']: 52 | Box(copper, 10, start, stop, padname = '2', mirror=m) 53 | 54 | # ground via 55 | start = np.array([-0.5*box_length, -0.5*ms_width-1.0e-3, substrate_top]) 56 | stop = np.array([0.5*box_length, -0.5*box_width, substrate_bottom]) 57 | for m in ['', 'y']: 58 | Box(copper, 10, start, stop, padname = None, mirror=m) 59 | 60 | # port (ms) 61 | start = [-0.5*box_length, ms_width/2.0, substrate_top] 62 | stop = [-0.5*box_length + port_length, ms_width/-2.0, foil_top] 63 | Port(em, start, stop, direction='x', z=50) 64 | 65 | # shield 66 | start = np.array([0.5*box_length, 0.5*box_width, substrate_bottom]) 67 | stop = np.array([-0.5*box_length, -0.5*box_width, -0.5*box_height]) 68 | Box(copper_shield, 1, start, stop, padname = None) 69 | 70 | # dielectric 71 | start = np.array([0, 0, substrate_bottom]) 72 | stop = np.array([0, 0, -0.5*box_height]) 73 | Cylinder(teflon, 2, start, stop, 0.5*dielectric_diameter) 74 | 75 | # pin 76 | start = np.array([0, 0, substrate_top + 0.5e-3]) 77 | stop = np.array([0, 0, -0.5*box_height + coax_port_length]) 78 | Cylinder(copper, 3, start, stop, 0.5*pin_diameter) 79 | 80 | # port (coax) 81 | start = [0.5*coax_port_length, 0.5*coax_port_length, -0.5*box_height + coax_port_length] 82 | stop = [-0.5*coax_port_length, -0.5*coax_port_length, -0.5*box_height] 83 | Port(em, start, stop, direction='z', z=50) 84 | 85 | em.write_kicad(em.name) 86 | command = 'view solve' 87 | if len(sys.argv) > 1: 88 | command = sys.argv[1] 89 | print(command) 90 | em.run_openems(command) 91 | -------------------------------------------------------------------------------- /examples/sss_idbpf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from scipy.constants import pi, c, mil 4 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric, Polygon, arc 5 | import numpy as np 6 | 7 | em = OpenEMS( 8 | 'sss_idbpf', 9 | EndCriteria = 1e-6, 10 | fmin = 0e6, 11 | fmax = 40e9, 12 | fsteps = 1001, 13 | boundaries = ['PEC', 'PEC', 'PEC', 'PEC', 'PEC', 'PEC'], 14 | ) 15 | copper = Metal(em, 'copper') 16 | sub = Dielectric(em, 'polyimide', eps_r=3.2, tand=0.0035, fc=em.fmax) 17 | 18 | foil_thickness = 35e-6 19 | substrate_thickness = 4*mil 20 | port_length = 0.065e-3 21 | box_length = 14e-3 22 | ms_width = 0.6e-3 23 | port_length = 0.065e-3 24 | 25 | lengths = 3.0e-3*np.ones(4) 26 | gaps = [0.2e-3, 0.68e-3, 0.85e-3, 0.9e-3] 27 | rw = 1e-3 28 | 29 | # dimensions Z 30 | zair = 1e-3 31 | z0 = 0 32 | z1 = z0 + 1e-3 # bottom of bottom foil 33 | z2 = z1 + foil_thickness # top of bottom foil 34 | z3 = z2 + substrate_thickness # bottom of top foil 35 | z4 = z3 + foil_thickness # top of top foil 36 | z5 = z4 + zair 37 | 38 | em.resolution = [100e-6, 100e-6, 100e-6] 39 | 40 | em.mesh.AddLine('z', z0) 41 | em.mesh.AddLine('z', z5) 42 | em.mesh.AddLine('z', (2*z2+z3)*0.33) 43 | em.mesh.AddLine('z', (2*z3+z2)*0.33) 44 | 45 | endgap = 0.3e-3 46 | 47 | y = -0.5*(np.max(lengths)+endgap) 48 | 49 | # resonators 50 | x = -0.5*gaps[-1] 51 | ym = -1 52 | 53 | for i in range(len(lengths))[::-1]: 54 | ym *= -1 55 | x += gaps[i] 56 | l = lengths[i] 57 | x1 = x + rw 58 | yr = y+endgap if i==0 else y 59 | for m in [-1,1]: 60 | start = np.array([x*m, m*ym*yr, z3]) 61 | stop = np.array([x1*m, m*ym*(y+l), z4]) 62 | Box(copper, 1, start, stop, padname='poly') 63 | x = x1 64 | 65 | ymin = y 66 | ymax = -y 67 | ledge = 0.5e-3 68 | yshift = 0e-3 69 | 70 | # port (ms), port line 71 | for m in [-1,1]: 72 | start = np.array([m*(0.5*box_length-port_length), ym*(y+endgap)*m, z3]) 73 | stop = np.array([m*x, ym*(y+endgap+ms_width)*m, z4]) 74 | Box(copper, 1, start, stop, padname = None) 75 | stop[0] = m*0.5*box_length 76 | Port(em, start, stop, direction='x', z=50) 77 | # end grounds 78 | start = np.array([m*0.5*box_length, ymin, z1]) 79 | stop = np.array([m*(x+0.3e-3), ymax, z2]) 80 | Box(copper, 1, start, stop, padname = None) 81 | 82 | # top and bottom grounds 83 | for y in [[ymin, ymin-ledge+yshift], [ymax, ymax+ledge+yshift]]: 84 | for z in [[z3, z4], [z1, z2]]: 85 | start = np.array([m*0.5*box_length, y[0], z[0]]) 86 | stop = np.array([m*-0.5*box_length, y[1], z[1]]) 87 | Box(copper, 1, start, stop, padname = None) 88 | 89 | # substrate 90 | start = np.array([-0.5*box_length, ymin, z2]) 91 | stop = np.array([0.5*box_length, ymax, z3]) 92 | Box(sub, 1, start, stop) 93 | 94 | em.write_kicad(em.name) 95 | command = 'view solve' 96 | if len(sys.argv) > 1: 97 | command = sys.argv[1] 98 | print(command) 99 | em.run_openems(command) 100 | -------------------------------------------------------------------------------- /examples/stripline/line.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from scipy.constants import pi, c, epsilon_0, mu_0, mil 4 | mm = 0.001 5 | import openems 6 | import numpy as np 7 | 8 | em = openems.OpenEMS('line', EndCriteria = 1e-6) 9 | em.fmin = 1e6 10 | em.fmax = 40e9 11 | em.fsteps = 1601 12 | fc = 40e9 13 | pec = openems.Metal(em, 'pec') 14 | sub = openems.Dielectric(em, 'fr4', eps_r=3.9, tand=0.0035, fc=fc) 15 | foil_thickness = 0.6*mil 16 | substrate_thickness = 0.2*mm 17 | ms_air_above = 0.36*mm 18 | port_length = 0.1*mm 19 | box_length = 10*mm 20 | box_width = 1*mm 21 | ms_width = 0.2*mm 22 | # dimensions Z 23 | substrate_bottom = 0.0 24 | substrate_top = substrate_bottom + substrate_thickness 25 | foil_top = substrate_top + foil_thickness 26 | 27 | from math import sqrt 28 | em.resolution = c/(em.fmax*sqrt(3.0))/100.0 29 | 30 | # substrate 31 | start = np.array([0.5*box_length, 0.5*box_width, substrate_bottom]) 32 | stop = openems.mirror(start, 'xy') + np.array([0, 0, substrate_top+ms_air_above]) 33 | sub.AddBox(start, stop, 1); 34 | 35 | # line 36 | start = np.array([0.5*box_length-port_length, 0.5*ms_width, substrate_top]) 37 | stop = np.array([0, -0.5*ms_width, foil_top]) 38 | for m in [[1,1,1], [-1,1,1]]: 39 | pec.AddBox(start*m, stop*m, 2) 40 | 41 | # ports 42 | start = np.array([-0.5*box_length, ms_width/2.0, substrate_top]) 43 | stop = np.array([-0.5*box_length + port_length, ms_width/-2.0, foil_top]) 44 | for m in [[1,1,1], [-1,-1,1]]: 45 | openems.Port(em, start*m, stop*m, direction='x', z=50) 46 | 47 | em.write_kicad(em.name) 48 | command = 'view solve' 49 | if len(sys.argv) > 1: 50 | command = sys.argv[1] 51 | print(command) 52 | em.run_openems(command) 53 | -------------------------------------------------------------------------------- /examples/stripline_ecbpf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from scipy.constants import pi, c, mil 4 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric, Polygon, arc 5 | import numpy as np 6 | 7 | band = 5 8 | 9 | g = np.ones(10) * 0.28e-3 10 | g[0] = 0.18e-3 11 | 12 | cw = 4e-3 13 | mesh = [53e-6, 53e-6, 53e-6] 14 | 15 | # 7 updated 16 | w = np.array([7.25,15.75,19.25,19.25])*mil 17 | s = np.array([7.25,11.5,15.75,16.25])*mil 18 | 19 | boundaries = ['PEC', 'PEC', 'PEC', 'PEC', 'PEC', 'PEC'] 20 | 21 | if band == 1: 22 | fc = 16.95e9*.985 23 | fmax = 25e9 24 | g = np.ones(10) * 0.28e-3 25 | g[0] = 0.15e-3 26 | mesh = [34e-6, 32e-6, 27e-6] 27 | # 12.09183645866654 28 | 29 | elif band == 2: 30 | fc = 21.35e9*0.9875#/(21.62/21.35) 31 | fmax = 30e9 32 | mesh = [34e-6, 32e-6, 27e-6] 33 | #14.964134888145727 34 | 35 | elif band == 3: 36 | fc = 26.9e9*0.9955 37 | fmax = 35e9 38 | mesh = [34e-6, 32e-6, 27e-6] 39 | #18.483322945104838 40 | 41 | elif band == 4: 42 | fc = 33.9e9*0.9955 43 | fmax = 50e9 44 | mesh = [52e-6, 52e-6, 35e-6] 45 | boundaries = ['PEC', 'PEC', 'MUR', 'MUR', 'PEC', 'PEC'] 46 | 47 | elif band == 5: 48 | fc = 42.7e9 49 | fmax = 60e9 50 | mesh = [52e-6, 52e-6, 35e-6] 51 | boundaries = ['PEC', 'PEC', 'MUR', 'MUR', 'PEC', 'PEC'] 52 | 53 | elif band == 6: 54 | fc = 53.8e9 55 | fmax = 70e9 56 | mesh = [52e-6, 52e-6, 35e-6] 57 | #g *= 0 58 | boundaries = ['PEC', 'PEC', 'MUR', 'MUR', 'PEC', 'PEC'] 59 | 60 | else: 61 | raise Exception("undefined band") 62 | 63 | em = OpenEMS( 64 | 'sss_ecbpf{}'.format(band), 65 | EndCriteria = 1e-6, 66 | fmin = 0e6, 67 | fmax = fmax, 68 | fsteps = 1001, 69 | boundaries = boundaries, 70 | ) 71 | 72 | copper = Metal(em, 'copper') 73 | sub = Dielectric(em, 'polyimide', eps_r=3.2, tand=0.0035, fc=fc) 74 | zport = 100 75 | foil_thickness = 0.05e-3 76 | substrate_thickness = 4*mil 77 | port_length = 0.42e-3 78 | 79 | sl_width = 0.597e-3 80 | 81 | qw = 0.25 * c / (fc * np.sqrt(1.35)) 82 | print("quarter wave length:", qw) 83 | 84 | etch = 0e-6 85 | g += 0.5*etch 86 | w -= etch 87 | s += etch 88 | 89 | # dimensions Z 90 | zair = 0.75e-3 91 | z0 = 0 92 | z1 = z0 + zair # bottom of bottom foil 93 | z2 = z1 + foil_thickness # top of bottom foil 94 | z3 = z2 + substrate_thickness # bottom of top foil 95 | z4 = z3 + foil_thickness # top of top foil 96 | z5 = z4 + zair 97 | 98 | em.resolution = mesh 99 | 100 | em.mesh.AddLine('z', z0) 101 | em.mesh.AddLine('z', z5) 102 | em.mesh.AddLine('z', z4 + 50e-6) 103 | #em.mesh.AddLine('z', z4 - 25e-6) 104 | em.mesh.AddLine('z', (z3+z2)*0.5) 105 | #em.mesh.AddLine('z', (3*z3+z2)*0.25) 106 | 107 | x = 0 108 | y = 0.5*w[-1] 109 | 110 | # resonators 111 | for i in range(len(w))[::-1]: 112 | gp = g[0] 113 | for m in [-1,1]: 114 | start = np.array([(x-gp)*m, (y-w[i])*m, z3]) 115 | stop = np.array([(x+qw-g[i])*m, y*m, z4]) 116 | Box(copper, 1, start, stop, padname='poly') 117 | y += s[i] 118 | for m in [-1,1]: 119 | start = np.array([(x+g[i])*m, y*m, z3]) 120 | stop = np.array([(x+qw)*m, (y+w[i])*m, z4]) 121 | Box(copper, 1, start, stop, padname='poly') 122 | y += w[i] 123 | x += qw 124 | 125 | # port (ms), port line 126 | y -= w[0] 127 | hl = x + .75e-3 128 | pname = 1 129 | for m in [-1,1]: 130 | start = np.array([m*(hl-port_length), m*y, z3]) 131 | stop = np.array([m*x, m*(y+sl_width), z4]) 132 | Box(copper, 1, start, stop, padname = str(pname)) 133 | pname += 1 134 | stop[0] = m*hl 135 | Port(em, start, stop, direction='x', z=zport) 136 | 137 | y += sl_width 138 | theta = np.arctan(y/hl) 139 | print("theta =", theta*180/np.pi) 140 | yoff = 0.5*cw/np.cos(theta) 141 | print("yoff =", yoff) 142 | ymax = y + yoff 143 | 144 | points = np.array([[-hl, -ymax], [hl, -ymax], [hl, ymax-2*yoff]]) 145 | for m in ['xy', '']: 146 | if boundaries[2] == 'PEC': 147 | Polygon(copper, points, [z0, z5], x=0, y=0, mirror=m, priority=9) 148 | 149 | theta = np.arctan((2*ymax-2*yoff)/(2*hl)) 150 | print("theta =", theta*180/np.pi) 151 | 152 | # substrate 153 | start = np.array([-hl, -ymax, z2]) 154 | stop = np.array([hl, ymax, z3]) 155 | Box(sub, 1, start, stop) 156 | 157 | em.write_kicad(em.name) 158 | command = 'view solve' 159 | if len(sys.argv) > 1: 160 | command = sys.argv[1] 161 | print(command) 162 | em.run_openems(command, z=zport) 163 | -------------------------------------------------------------------------------- /examples/stripline_ss100.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from scipy.constants import pi, c, mil 4 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric, Polygon, arc 5 | import numpy as np 6 | use_PML_coax = True 7 | 8 | em = OpenEMS( 9 | 'sss_bpf', 10 | EndCriteria = 1e-6, 11 | fmin = 0e6, 12 | fmax = 50e9, 13 | fsteps = 1001, 14 | #boundaries = ['PEC', 'PEC', 'PML_8', 'PML_8', 'PEC', 'PEC'], 15 | ) 16 | fc = 16.95e9 17 | copper = Metal(em, 'copper') 18 | sub = Dielectric(em, 'ro4350b', eps_r=3.2) 19 | zport = 100 20 | 21 | foil_thickness = 0.05e-3 22 | substrate_thickness = 4*mil 23 | port_length = 0.42e-3 24 | box_width = 3e-3 25 | box_length = 20e-3 26 | ms_width = 0.56e-3 27 | 28 | # dimensions Z 29 | zair = 0.7e-3 30 | z0 = 0 31 | z1 = z0 + zair # bottom of bottom foil 32 | z2 = z1 + foil_thickness # top of bottom foil 33 | z3 = z2 + substrate_thickness # bottom of top foil 34 | z4 = z3 + foil_thickness # top of top foil 35 | z5 = z4 + zair 36 | 37 | em.resolution = [50e-6, 50e-6, 50e-6] 38 | 39 | em.mesh.AddLine('z', z0) 40 | em.mesh.AddLine('z', z5) 41 | em.mesh.AddLine('z', z4 + 50e-6) 42 | #em.mesh.AddLine('z', z4 - 25e-6) 43 | em.mesh.AddLine('z', (z3+z2)*0.5) 44 | #em.mesh.AddLine('z', (3*z3+z2)*0.25) 45 | 46 | # substrate 47 | start = np.array([-0.5*box_length, 0.5*box_width, z2]) 48 | stop = np.array([0.5*box_length, -0.5*box_width, z3]) 49 | Box(sub, 1, start, stop) 50 | 51 | for m in [-1,1]: 52 | start = np.array([m*(0.5*box_length-port_length), 0.5*ms_width, z3]) 53 | stop = np.array([0, -0.5*ms_width, z4]) 54 | Box(copper, 1, start, stop, padname = None) 55 | stop[0] = m*0.5*box_length 56 | Port(em, start, stop, direction='x', z=zport) 57 | 58 | em.write_kicad(em.name) 59 | command = 'view solve' 60 | if len(sys.argv) > 1: 61 | command = sys.argv[1] 62 | print(command) 63 | em.run_openems(command, z=zport) 64 | -------------------------------------------------------------------------------- /examples/transition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from scipy.constants import pi, c, mil 4 | from openems import OpenEMS, Box, Cylinder, Port, Metal, Dielectric, Polygon, arc 5 | import numpy as np 6 | use_PML_coax = True 7 | 8 | em = OpenEMS( 9 | 'suspended_substrate_stripline', 10 | EndCriteria = 1e-6, 11 | fmin = 0e6, 12 | fmax = 60e9, 13 | fsteps = 1001, 14 | boundaries = ['PEC', 'PML_16' if use_PML_coax else 'PEC', 'PEC', 'PEC', 'PEC', 'PEC'], 15 | ) 16 | copper = Metal(em, 'copper') 17 | copper_shield = Metal(em, 'copper_shield') 18 | copper_ms = Metal(em, 'copper_ms') 19 | sub = Dielectric(em, 'ro4350b', eps_r=3.2, tand=0.0035, fc=em.fmax) 20 | teflon = Dielectric(em, 'teflon', eps_r=2.1, tand=0.0002, fc=em.fmax) 21 | 22 | foil_thickness = 0.035e-3 23 | substrate_thickness = 4*mil 24 | port_length = 0.15e-3 25 | box_width = 2e-3 26 | box_length = 5e-3 27 | ms_width = 0.21e-3 28 | 3 29 | # coax 30 | pin_radius = 254e-6 31 | dielectric_radius = 0.5*67*mil 32 | coax_port_length = 0.2e-3 33 | 34 | # dimensions Z 35 | foil_top = 0 36 | substrate_top = foil_top - foil_thickness 37 | substrate_bottom = substrate_top - substrate_thickness 38 | 39 | em.resolution = [50e-6, 25e-6, 25e-6] 40 | 41 | em.mesh.AddLine('z', substrate_bottom - 1e-3) 42 | em.mesh.AddLine('z', substrate_top + 1e-3) 43 | 44 | # substrate 45 | start = np.array([-0.5*box_length, 0.5*box_width, substrate_bottom]) 46 | stop = np.array([0.0, -0.5*box_width, substrate_top]) 47 | Box(sub, 1, start, stop); 48 | 49 | # line 50 | port_length = 0.065e-3 51 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, substrate_top]) 52 | stop = np.array([0, -0.5*ms_width, foil_top]) 53 | Box(copper_ms, 1, start, stop, padname = None) 54 | 55 | # port (ms) 56 | stop[0] = -0.5*box_length 57 | Port(em, start, stop, direction='x', z=50) 58 | 59 | # pad 60 | ypad = 0.6e-3 61 | xpad = -0.62e-3 62 | xpad2 = -0.8e-3 63 | 64 | points = np.array([ 65 | [0, ypad], [xpad, ypad], [xpad2, 0], [xpad, -ypad], [0, -ypad]]) 66 | Polygon(copper, points, [substrate_top, foil_top], is_custom_pad=True, pad_name='1', x=0.5*xpad, y=0) 67 | 68 | z1 = substrate_bottom - foil_thickness 69 | 70 | gp = Metal(em, 'groundplane') 71 | y1 = ypad+200e-6 72 | x1 = xpad2-00e-6 73 | points = np.array([ 74 | [x1,y1], [0, y1], [0, 0.5*box_width], [-0.5*box_length, 0.5*box_width]]) 75 | Polygon( 76 | copper, 77 | np.concatenate((points, points[::-1]*[1,-1])), 78 | [z1, substrate_bottom], is_custom_pad=True, pcb_layer='B.Cu', pad_name='2', x=x1-0.3e-3, y=0) 79 | 80 | em.write_kicad(em.name) 81 | command = 'view solve' 82 | if len(sys.argv) > 1: 83 | command = sys.argv[1] 84 | print(command) 85 | em.run_openems(command) 86 | -------------------------------------------------------------------------------- /examples/vert_connector_ms_oshpark.kicad_mod: -------------------------------------------------------------------------------- 1 | (module coax_compression (layer F.Cu) (tedit 5C589EFB) 2 | (descr DocString) 3 | (tags Keywords) 4 | (solder_mask_margin 0.0762) 5 | (clearance 0.152) 6 | (zone_connect 2) 7 | (attr smd) 8 | (fp_text reference J? (at 0 -5.85) (layer F.SilkS) 9 | (effects (font (size 1 1) (thickness 0.127))) 10 | ) 11 | (fp_text value ? (at 0 1.27) (layer Cmts.User) hide 12 | (effects (font (size 1.016 1.016) (thickness 0.127))) 13 | ) 14 | (fp_circle (center 0 -3.6) (end 2.4 -3.6) (layer Cmts.User) (width 0.12)) 15 | (fp_circle (center 0 3.6) (end 2.4 3.6) (layer Cmts.User) (width 0.12)) 16 | (fp_poly (pts (xy -2.9 -4.2) (xy -2.9 4.2) (xy -1.45 5) (xy 1.45 5) 17 | (xy 2.9 4.2) (xy 2.9 -4.2) (xy 1.45 -5) (xy -1.45 -5)) (layer F.Mask) (width 0.2)) 18 | (fp_text user value (at 0 1.27) (layer F.SilkS) hide 19 | (effects (font (size 0.7 0.7) (thickness 0.127))) 20 | ) 21 | (fp_text user U1 (at 0 -1.27) (layer F.SilkS) 22 | (effects (font (size 0.7 0.7) (thickness 0.127))) 23 | ) 24 | (pad 2 thru_hole circle (at 0 -3.58) (size 2.5 2.5) (drill 1.6) (layers *.Cu *.Mask) 25 | (zone_connect 2)) 26 | (pad 2 thru_hole circle (at 0 3.58) (size 2.5 2.5) (drill 1.6) (layers *.Cu *.Mask) 27 | (zone_connect 2)) 28 | (pad 2 thru_hole circle (at -0.669131 -0.743145) (size 0.5 0.5) (drill 0.25) (layers *.Cu *.Mask) 29 | (solder_mask_margin -0.0325) (zone_connect 2)) 30 | (pad 2 thru_hole circle (at 0.71934 0.694658) (size 0.5 0.5) (drill 0.25) (layers *.Cu *.Mask) 31 | (solder_mask_margin -0.0325) (zone_connect 2)) 32 | (pad 2 thru_hole circle (at 0.034899 -0.999391) (size 0.5 0.5) (drill 0.25) (layers *.Cu *.Mask) 33 | (solder_mask_margin -0.0325) (zone_connect 2)) 34 | (pad 2 thru_hole circle (at 0.034899 0.999391) (size 0.5 0.5) (drill 0.25) (layers *.Cu *.Mask) 35 | (solder_mask_margin -0.0325) (zone_connect 2)) 36 | (pad 2 thru_hole circle (at 1 0) (size 0.5 0.5) (drill 0.25) (layers *.Cu *.Mask) 37 | (solder_mask_margin -0.0325) (zone_connect 2)) 38 | (pad 2 thru_hole circle (at -0.669131 0.743145) (size 0.5 0.5) (drill 0.25) (layers *.Cu *.Mask) 39 | (solder_mask_margin -0.0325) (zone_connect 2)) 40 | (pad 2 thru_hole circle (at 0.71934 -0.694658) (size 0.5 0.5) (drill 0.25) (layers *.Cu *.Mask) 41 | (solder_mask_margin -0.0325) (zone_connect 2)) 42 | (pad 1 thru_hole circle (at 0 0) (size 0.5 0.5) (drill 0.25) (layers *.Cu *.Mask) 43 | (solder_mask_margin -0.0325) (clearance 0.275) (zone_connect 2)) 44 | (pad 1 smd circle (at 0 0) (size 0.5 0.5) (layers F.Cu F.Paste F.Mask) 45 | (solder_mask_margin -0.0325) (clearance 0.5) (zone_connect 2)) 46 | (model "/home/dlharmon/vna/library/3d/HRM(G)-300-468B-1.stp" 47 | (offset (xyz 0 0 4.5)) 48 | (scale (xyz 1 1 1)) 49 | (rotate (xyz 90 0 90)) 50 | ) 51 | ) 52 | -------------------------------------------------------------------------------- /examples/vert_connector_ms_oshpark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | mm = 0.001 4 | import openems 5 | import openems.geometries 6 | import numpy as np 7 | 8 | em = openems.OpenEMS('vert_connector_ms_oshpark', EndCriteria = 1e-5, fmin = 0e6, fmax = 50e9, 9 | boundaries = ['PEC', 'PEC', 'PEC', 'PEC', 'PML_12', 'PEC']) 10 | em.fsteps = 1601 11 | copper = openems.Metal(em, 'copper') 12 | pcopper = openems.Metal(em, 'pcopper') 13 | sub1 = openems.Dielectric(em, 'substrate', eps_r=3.2) 14 | sub2 = openems.Dielectric(em, 'substrate', eps_r=4.0) 15 | 16 | sub1t = 0.19*mm 17 | sub2t = 1.0*mm 18 | 19 | ifoil = 0.0125*mm 20 | ofoil = 0.035*mm 21 | port_length = 0.1*mm 22 | box_length = 2*mm 23 | box_width = 2*mm 24 | ms_width = 0.42*mm 25 | airspace = 1*mm 26 | via_pad = 0.5*mm 27 | via_clearance = 0.275*mm 28 | 29 | bt = sub1t + ofoil 30 | bb = -1*(ofoil+sub2t+sub1t) 31 | 32 | em.resolution = 25e-6 33 | 34 | em.mesh.AddLine('z', sub1t+airspace) 35 | zmin = bb - 1*mm 36 | em.mesh.AddLine('z', zmin) 37 | 38 | planar = openems.geometries.planar_full_box(x=[-0.5*box_length, 0.5*box_length], 39 | y=[-0.5*box_width, 0.5*box_width]) 40 | 41 | clearance_r = via_pad*0.5 + via_clearance 42 | planar.add(sub1, [0, sub1t], priority=1) # sub1 top 43 | planar.add_center_hole(pcopper, [0, ifoil], clearance_r, priority=2) # inner 1 foil 44 | planar.add(sub2, [0, -sub2t], priority=1) # sub2 45 | planar.add(sub1, [-sub2t, -(sub2t+sub1t)], priority=1) # sub1 bottom 46 | planar.add_center_hole(pcopper, [-sub2t, -(sub2t+ifoil)], clearance_r, priority=2) # inner2 foil 47 | planar.add_center_hole(pcopper, [bb, bb+ofoil], 0.75*mm, priority=1) # bottom foil 48 | 49 | # ms line 50 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, sub1t]) 51 | stop = np.array([0, -0.5*ms_width, bt]) 52 | copper.AddBox(start, stop, priority=9) 53 | 54 | # ms port 55 | start = [-0.5*box_length, ms_width/2.0, sub1t] 56 | stop = [-0.5*box_length + port_length, ms_width/-2.0, bt] 57 | openems.Port(em, start, stop, direction='x', z=50) 58 | 59 | via_z = [[bt,bb],[bt, bt-ofoil], [0, ifoil], [-sub2t, -sub2t-ifoil], [bb+ofoil, bb]] 60 | 61 | # ground vias 62 | for n in range(-3,4): 63 | r = 1 * mm 64 | c = np.exp(1j*2*np.pi*n*22.0/180.0) * r 65 | openems.Via(copper, priority=9, x=np.real(c), y=np.imag(c), z=via_z, 66 | drillradius = 0.25*mm*0.5, 67 | wall_thickness = 25e-6, 68 | padradius = via_pad * 0.5, 69 | padname='2') 70 | 71 | # signal via 72 | openems.Via(copper, priority=9, x=0, y=0, 73 | z=[[bt,bb],[bt, bt-ofoil], [0, ifoil], [-sub2t, -sub2t-ifoil], [bb+ofoil, bb]], 74 | drillradius = 0.25*mm*0.5, 75 | wall_thickness = 25e-6, 76 | padradius = via_pad * 0.5, 77 | padname='1') 78 | 79 | # coax shield 80 | planar.add_center_hole(copper, [bb, zmin], r=1.5*mm/2.0, priority=1) 81 | 82 | pin_diameter = 0.695*mm 83 | coax_port_length = 0.2*mm 84 | 85 | # pin 86 | start = np.array([0, 0, bb]) 87 | stop = np.array([0, 0, zmin]) 88 | copper.AddCylinder(start, stop, 0.5*pin_diameter, priority=9) 89 | 90 | # coax goes into Z- PML 91 | 92 | command = 'view solve' 93 | if len(sys.argv) > 1: 94 | command = sys.argv[1] 95 | em.write_kicad(em.name) 96 | em.run_openems(command) 97 | -------------------------------------------------------------------------------- /examples/via_1_3_oshpark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from openems import OpenEMS, Box, Cylinder, Via, Port, Metal, Dielectric, geometries 4 | import numpy as np 5 | em = OpenEMS('via_1_3_oshpark', EndCriteria = 1e-4, fmin = 0, fmax = 40e9, 6 | boundaries = ['PEC', 'PEC', 'PEC', 'PEC', 'PEC', 'PEC'], 7 | fsteps=801) 8 | copper = Metal(em, 'copper') 9 | pcopper = Metal(em, 'pcopper') 10 | sub1 = Dielectric(em, 'substrate', eps_r=3.2) 11 | sub2 = Dielectric(em, 'substrate', eps_r=4) 12 | 13 | sub1t = 0.166e-3 14 | sub2t = 47*25.4e-6 15 | ifoil = 0.0125e-3 16 | ofoil = 0.035e-3 17 | port_length = 0.1e-3 18 | box_length = 5e-3 19 | box_width = 2e-3 20 | sl_width = 0.24e-3 21 | ms_width = 0.35e-3 22 | airspace = 1e-3 23 | 24 | bt = sub1t + ofoil 25 | bb = -1*(ofoil+sub2t+sub1t) 26 | 27 | em.resolution = 50e-6 28 | 29 | planar = geometries.planar_full_box( 30 | x=[-0.5*box_length, 0.5*box_length], 31 | y=[-0.5*box_width, 0.5*box_width]) 32 | 33 | clearance_r = 0.86e-3 * 0.5 34 | 35 | em.mesh.AddLine('z', sub1t+airspace) 36 | em.mesh.AddLine('z', -1.0*(2*sub1t+sub2t+airspace)) 37 | 38 | planar.add(sub1, [0, sub1t]) # sub1 top 39 | planar.add_center_hole(pcopper, [0, ifoil], clearance_r, priority=2) # inner 1 foil 40 | planar.add(sub2, [0, -sub2t]) # core 41 | planar.add(sub1, [-sub2t, -(sub2t+sub1t)]) # sub1 bot 42 | planar.add_center_hole(pcopper, [bb, -(sub2t+sub1t)], clearance_r, priority=2) # bottom foil 43 | 44 | # line (sl) 45 | start = np.array([0, 0.5*sl_width, -sub2t]) 46 | stop = np.array([0.5*box_length-port_length, -0.5*sl_width, -sub2t-ifoil]) 47 | Box(copper, 9, start, stop) 48 | 49 | # line (ms) 50 | Box(copper, 9, [-0.5*box_length+port_length, 0.5*ms_width, sub1t], [0, -0.5*ms_width, bt]) 51 | 52 | # port (ms) 53 | start = [-0.5*box_length, ms_width/2.0, sub1t] 54 | stop = [-0.5*box_length + port_length, ms_width/-2.0, bt] 55 | Port(em, start, stop, direction='x', z=50) 56 | 57 | # port (sl) 58 | start = [0.5*box_length, sl_width/2.0, -sub2t] 59 | stop = [0.5*box_length - port_length, sl_width/-2.0, -sub2t-ifoil] 60 | Port(em, start, stop, direction='x', z=50) 61 | 62 | Via(copper, priority=9, x=0, y=0, 63 | z=[[bb, bt], [bt, bt-ofoil], [0, ifoil], [-sub2t, -sub2t-ifoil], [bb+ofoil, bb]], 64 | drillradius = 0.5*0.25e-3, 65 | wall_thickness = 25e-6, 66 | padradius = 0.46e-3*0.5, 67 | padname = '1') 68 | 69 | for x in range(-3,4): 70 | x *= 0.5e-3 71 | for y in [-0.75e-3, 0.75e-3]: 72 | Cylinder(copper, 9, [x, y, bb], [x, y, bt], 0.3e-3*0.5) 73 | Cylinder(copper, 9, [x, y, bt], [x, y, bt-ofoil], 0.46e-3*0.5) 74 | 75 | command = 'view solve' 76 | if len(sys.argv) > 1: 77 | command = sys.argv[1] 78 | em.run_openems(command) 79 | -------------------------------------------------------------------------------- /examples/via_1_4_oshpark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | from scipy.constants import pi, c, mil 4 | mm = 0.001 5 | from openems import OpenEMS, Box, Cylinder, Via, Port, Metal, Dielectric, geometries 6 | import numpy as np 7 | 8 | em = OpenEMS('via_1_4_oshpark', EndCriteria = 1e-4, fmin = 0e6, fmax = 40e9, 9 | boundaries = ['PEC', 'PEC', 'PEC', 'PEC', 'PEC', 'PEC']) 10 | em.fsteps = 1601 11 | copper = Metal(em, 'copper') 12 | pcopper = Metal(em, 'pcopper') 13 | sub1 = Dielectric(em, 'substrate', eps_r=3.2) 14 | sub2 = Dielectric(em, 'substrate', eps_r=4) 15 | air = Dielectric(em, 'substrate', eps_r=1) 16 | 17 | sub1t = 0.166*mm 18 | sub2t = 47*mil 19 | 20 | ifoil = 0.0125*mm 21 | ofoil = 0.035*mm 22 | port_length = 0.1*mm 23 | box_length = 5*mm 24 | box_width = 2*mm 25 | ms_width = 0.35*mm 26 | airspace = 1*mm 27 | 28 | bt = sub1t + ofoil 29 | bb = -1*(ofoil+sub2t+sub1t) 30 | 31 | em.resolution = 50e-6 32 | 33 | planar = geometries.planar_full_box(x=[-0.5*box_length, 0.5*box_length], 34 | y=[-0.5*box_width, 0.5*box_width]) 35 | 36 | em.mesh.AddLine('z', sub1t+airspace) 37 | em.mesh.AddLine('z', -1.0*(2*sub1t+sub2t+airspace)) 38 | 39 | clearance_r = 0.86e-3 * 0.5 40 | 41 | planar.add(sub1, [0, sub1t]) # sub1 top 42 | planar.add_center_hole(pcopper, [0, ifoil], clearance_r, priority=2) # inner 1 foil 43 | planar.add(sub2, [0, -sub2t]) # core 44 | planar.add_center_hole(pcopper, [-sub2t, -(sub2t+ifoil)], clearance_r, priority=2) # inner 2 foil 45 | planar.add(sub1, [-sub2t, -(sub2t+sub1t)]) # sub1 bot 46 | 47 | # line 48 | start = np.array([0, 0.5*ms_width, bb]) 49 | stop = np.array([0.5*box_length-port_length, -0.5*ms_width, bb+ofoil]) 50 | Box(copper, 9, start, stop) 51 | 52 | # line 53 | start = np.array([-0.5*box_length+port_length, 0.5*ms_width, sub1t]) 54 | stop = np.array([0, -0.5*ms_width, bt]) 55 | Box(copper, 9, start, stop) 56 | 57 | # ports 58 | start = [-0.5*box_length, ms_width/2.0, sub1t] 59 | stop = [-0.5*box_length + port_length, ms_width/-2.0, bt] 60 | Port(em, start, stop, direction='x', z=50) 61 | start = [0.5*box_length, ms_width/2.0, bb] 62 | stop = [0.5*box_length - port_length, ms_width/-2.0, bb+ofoil] 63 | Port(em, start, stop, direction='x', z=50) 64 | 65 | Via(copper, priority=9, x=0, y=0, 66 | z=[[bb, bt], [bt, bt-ofoil], [0, ifoil], [-sub2t, -sub2t-ifoil], [bb+ofoil, bb]], 67 | drillradius = 0.5*0.25e-3, 68 | wall_thickness = 25e-6, 69 | padradius = 0.46e-3*0.5, 70 | padname = '1') 71 | 72 | for x in range(-2,3): 73 | for y in [-0.75*mm, 0.75*mm]: 74 | Cylinder(copper, 9, [x*mm, y, bb], [x*mm, y, bt], 0.3*mm*0.5) 75 | Cylinder(copper, 9, [x*mm, y, bt], [x*mm, y, bt-ofoil], 0.46*mm*0.5) 76 | Cylinder(copper, 9, [x*mm, y, bb], [x*mm, y, bb+ofoil], 0.46*mm*0.5) 77 | 78 | command = 'view solve' 79 | if len(sys.argv) > 1: 80 | command = sys.argv[1] 81 | em.run_openems(command) 82 | -------------------------------------------------------------------------------- /examples/wilkinson_1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import openems 4 | from openems import wilkinson 5 | 6 | em = openems.OpenEMS('wilkinson_1', EndCriteria = 1e-4, fmin = 0e6, fmax = 6e9, fsteps=400) 7 | 8 | fc = 1e9 9 | sub = openems.Dielectric(em, 'fr408', eps_r=3.3, tand=0.012, fc=fc) 10 | 11 | foil_thickness = 35e-6 12 | substrate_thickness = 180e-6 13 | ms_air_above = 700e-6 14 | z = [0.0, substrate_thickness, substrate_thickness + foil_thickness] 15 | em.mesh.AddLine('z', z[2] + ms_air_above) 16 | 17 | em.resolution = 50e-6 18 | 19 | wilkinson.generate( 20 | em, 21 | y1 = 5e-3*1.43, 22 | y2 = 500e-6, 23 | r = 200e-6, 24 | rv = [None, None, None, 100], 25 | w = [150e-6]*4, 26 | substrate = sub, 27 | z = z, 28 | port_length = 100e-6, 29 | ms_width = 360e-6) 30 | 31 | em.write_kicad(em.name, mirror = '') 32 | command = 'view solve' 33 | if len(sys.argv) > 1: 34 | command = sys.argv[1] 35 | em.run_openems(command) 36 | -------------------------------------------------------------------------------- /examples/wilkinson_2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import openems 4 | from openems import wilkinson 5 | 6 | em = openems.OpenEMS('wilkinson_2', EndCriteria = 1e-4, fmin = 0e6, fmax = 6e9, fsteps=400) 7 | 8 | fc = 1e9 9 | sub = openems.Dielectric(em, 'fr408', eps_r=3.3, tand=0.012, fc=fc) 10 | 11 | foil_thickness = 35e-6 12 | substrate_thickness = 180e-6 13 | ms_air_above = 700e-6 14 | z = [0.0, substrate_thickness, substrate_thickness + foil_thickness] 15 | em.mesh.AddLine('z', z[2] + ms_air_above) 16 | 17 | em.resolution = 50e-6 18 | 19 | wilkinson.generate( 20 | em, 21 | y1 = 4.8e-3, 22 | y2 = 500e-6, 23 | r = 200e-6, 24 | rv = [None, None, 100], 25 | w = [150e-6]*4, 26 | substrate = sub, 27 | z = z, 28 | port_length = 100e-6, 29 | ms_width = 360e-6) 30 | 31 | em.write_kicad(em.name, mirror = '') 32 | command = 'view solve' 33 | if len(sys.argv) > 1: 34 | command = sys.argv[1] 35 | em.run_openems(command) 36 | -------------------------------------------------------------------------------- /examples/wilkinson_3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import openems 4 | from openems import wilkinson 5 | 6 | em = openems.OpenEMS('wilkinson_3', EndCriteria = 1e-4, fmin = 0e6, fmax = 6e9, fsteps=400) 7 | 8 | fc = 1e9 9 | sub = openems.Dielectric(em, 'fr408', eps_r=3.3, tand=0.012, fc=fc) 10 | 11 | foil_thickness = 35e-6 12 | substrate_thickness = 180e-6 13 | ms_air_above = 700e-6 14 | z = [0.0, substrate_thickness, substrate_thickness + foil_thickness] 15 | em.mesh.AddLine('z', z[2] + ms_air_above) 16 | 17 | em.resolution = 50e-6 18 | 19 | wilkinson.generate( 20 | em, 21 | y1 = 5e-3, 22 | y2 = 500e-6, 23 | r = 200e-6, 24 | rv = [None, 100], 25 | w = [150e-6]*4, 26 | substrate = sub, 27 | z = z, 28 | port_length = 100e-6, 29 | ms_width = 360e-6) 30 | 31 | em.write_kicad(em.name, mirror = '') 32 | command = 'view solve' 33 | if len(sys.argv) > 1: 34 | command = sys.argv[1] 35 | em.run_openems(command) 36 | -------------------------------------------------------------------------------- /geometries.py: -------------------------------------------------------------------------------- 1 | from openems import OpenEMS, Box, Cylinder, Via, Port, Metal, Dielectric, geometries, arc, Polygon 2 | import numpy as np 3 | 4 | def smp_connector(em, x, y, z, zmax, coax_port_length = 0.2e-3, pin_diameter=0.85e-3): 5 | copper_shield = Metal(em, 'smp_shield') 6 | copper = Metal(em, 'smp_pin') 7 | lcp = Dielectric(em, 'lcp', eps_r=3.2) 8 | 9 | # shield 10 | outside = np.array([[-2.5,1.0], [-2.5,2.5], [2.5,2.5], [2.5,-2.5], [-2.5,-2.5], [-2.5,-1.0]])*1e-3 11 | angle = np.arcsin(1.0/2.25) 12 | inside = arc(0,0, 4.5e-3 / 2.0, -np.pi + angle, np.pi - angle) 13 | Polygon(copper_shield, 14 | priority=9, 15 | pcb_layer=None, 16 | points = [x,y] + np.concatenate((inside, outside)), 17 | elevation = [z, z + 0.9e-3], 18 | normal_direction = 'z') 19 | 20 | outside = np.array([[0,2.5], [2.5,2.5], [2.5,-2.5], [0,-2.5]])*1e-3 21 | inside = arc(0,0, 1.95e-3 / 2.0, -np.pi*0.5, np.pi*0.5) 22 | for m in ['', 'x']: 23 | Polygon(copper_shield, 9, 24 | pcb_layer=None, 25 | points = [x,y] + np.concatenate((inside, outside)), 26 | elevation = [z + 0.9e-3, zmax], 27 | normal_direction = 'z', 28 | mirror=m) 29 | # pin 30 | start = np.array([x, y, zmax - coax_port_length]) 31 | stop = np.array([x, y, z + 0.9e-3]) 32 | Cylinder(copper, 9, start, stop, 0.5*pin_diameter) 33 | 34 | # smaller part of pin 35 | Cylinder(copper, 9, start, [x, y, z], 0.5*0.8e-3) 36 | 37 | # insulator 38 | Cylinder(lcp, 1, [x, y, z], [x, y, z + 0.9e-3], 2.25e-3) 39 | 40 | if coax_port_length != 0: 41 | # port (coax) 42 | start = [x + 0.5*coax_port_length, y + 0.5*coax_port_length, zmax - coax_port_length] 43 | stop = [x - 0.5*coax_port_length, y - 0.5*coax_port_length, zmax] 44 | Port(em, start, stop, direction='z', z=50) 45 | em.mesh.AddLine('z', start[2]) 46 | 47 | class planar_full_box: 48 | def __init__(self, x, y): 49 | self.x = x 50 | self.y = y 51 | def add(self, sub, z, priority=1): 52 | start = np.array([self.x[0], self.y[0], z[0]]) 53 | stop = np.array([self.x[1], self.y[1], z[1]]) 54 | sub.AddBox(start, stop, priority=priority) 55 | 56 | # add a plane with a hole in the middle 57 | def add_center_hole(self, sub, z, r, priority=1, pcb_layer = None): 58 | outside = np.array([[0,self.y[1]], 59 | [self.x[1],self.y[1]], 60 | [self.x[1],self.y[0]], 61 | [0,self.y[0]]]) 62 | inside = arc(0,0, r, -np.pi*0.5, np.pi*0.5) 63 | for m in ['', 'x']: 64 | Polygon(sub, 65 | priority=priority, 66 | pcb_layer=pcb_layer, 67 | points = np.concatenate((inside, outside)), 68 | elevation = z, 69 | normal_direction = 'z', 70 | mirror = m 71 | ) 72 | -------------------------------------------------------------------------------- /idbpf.py: -------------------------------------------------------------------------------- 1 | mm = 0.001 2 | from openems import OpenEMS, Box, Cylinder, Via, Port, Metal, Dielectric, geometries, mirror 3 | import numpy as np 4 | 5 | def idbpf( 6 | em, # openems instance 7 | sub, # substrate, define with Dielectric() 8 | z = [], # z position, z[0] = sub bottom, z[1] = sub top, z[2] = foil top 9 | lidz = 0, # upper substrate thickness 10 | rl = [], # length of resonator fingers 11 | rw = [], # width of resonator fingers 12 | space = [], # space between resonator fingers 13 | endmetal = True, # add metal to filter ends 14 | portlength = 0.2*mm, # length of the openems port 15 | feedwidth = 0.85*mm, # width of the trace leaving the filter and port 16 | end_gap = 0.3*mm, # space between the end of a resonator and ground 17 | via_radius = 0.15*mm, # radius of the via drills 18 | via_padradius = 0.3*mm, # radius of the via pads 19 | pcb_layer = 'F.Cu', # Kicad layer 20 | mask = None, # mask, define with Dielectric() 21 | mask_thickness = 0, # set to non-zero to enable solder mask over filter 22 | ): 23 | edge_space = 0.5*mm 24 | pec = Metal(em, 'pec_filter') 25 | ring_ix = 0.5 * (np.max(rl) + end_gap) 26 | ring_ox = ring_ix + 2.0 * via_padradius 27 | via_z = [[z[0], z[1]+lidz], [z[1], z[2]]] 28 | # fingers 29 | y = -0.5*space[-1:][0] 30 | mirror = False 31 | mirrorstring = ['', 'x'] 32 | for i in range(len(space))[::-1]: # reverse order 33 | x1 = 0.5 * (ring_ox + ring_ix) 34 | x2 = ring_ix - rl[i] 35 | y += space[i] 36 | start = [x1, y, z[1]]; 37 | y += rw[i] 38 | stop = [x2, y, z[2]]; 39 | box = Box(pec, 9, start, stop, padname = 'poly', pcb_layer=pcb_layer) 40 | box.mirror(mirrorstring[mirror]) 41 | mirror = not mirror 42 | box2 = box.duplicate() 43 | box2.mirror('xy') 44 | if i == 0: 45 | continue 46 | v = Via( 47 | pec, priority=2, 48 | x=ring_ix+via_padradius, 49 | y=y-0.5*rw[i], z=via_z, 50 | drillradius=via_radius, 51 | padradius=via_padradius, padname='2') 52 | if not mirror: 53 | v.mirror('x') 54 | v.duplicate().mirror('xy') 55 | mirror = not mirror 56 | # ports 57 | y1 = y + feedwidth - rw[0] # outer edge of feed 58 | y2 = y - rw[0] # inner edge 59 | px = ring_ox 60 | start = [px, y2, z[1]] 61 | stop = [px - portlength, y1, z[2]] 62 | p = Port(em, start, stop, direction='x', z=50) 63 | p.mirror(mirrorstring[mirror]) 64 | p2 = p.duplicate().mirror('xy') 65 | # feed lines 66 | start = [-ring_ix + rl[0], y1, z[1]] 67 | stop = [px - portlength, y2, z[2]] 68 | box = Box(pec, 9, start, stop, padname = '1', pcb_layer=pcb_layer) 69 | box.mirror(mirrorstring[mirror]) 70 | box2 = box.duplicate() 71 | box2.mirror('xy') 72 | box2.padname = '3' 73 | # substrate 74 | start = np.array([ring_ox, y1 + edge_space, z[0]]) 75 | stop = mirror(start, 'xy') 76 | stop[2] = z[1]+lidz 77 | sub = Box(sub, 1, start, stop) 78 | # mask 79 | if mask_thickness > 0.0: 80 | start = np.array([ring_ox, y2, z[1]]) 81 | stop = mirror(start, 'xy') 82 | stop[2] += mask_thickness 83 | Box(mask, 1, start, stop) 84 | # grounded end metal 85 | if endmetal: 86 | for m in ['', 'xy']: 87 | Box(pec, 9, 88 | start = [ring_ix, -y2+space[0], z[1]], 89 | stop = [ring_ix + 2.0*via_padradius, y1+edge_space, z[2]], 90 | padname = '2', pcb_layer=pcb_layer, 91 | mirror=m) 92 | -------------------------------------------------------------------------------- /idbpf_tapped.py: -------------------------------------------------------------------------------- 1 | mm = 0.001 2 | import openems 3 | import numpy as np 4 | 5 | def idbpf(em, # openems instance 6 | sub, # substrate, define with openems.Dielectric() 7 | lidz = 0.0, 8 | z = [], # z position, z[0] = sub bottom, z[1] = sub top, z[2] = foil top 9 | space = [], # space between resonator fingers 10 | rl = [], # length of resonator fingers 11 | rw = [], # width of resonator fingers 12 | portlength = 0.2*mm, # length of the openems port 13 | feedwidth = 0.85*mm, # width of the trace leaving the filter and port 14 | tapoffset = 1.0*mm, # offset of tap from end of resonator 15 | end_gap = 0.3*mm, # space between the end of a resonator and ground 16 | via_radius = 0.15*mm, # radius of the via drills 17 | via_padradius = 0.3*mm, # radius of the via pads 18 | pcb_layer = 'F.Cu', # Kicad layer 19 | mask_thickness = 0, # set to non-zero to enable solder mask over filter 20 | mask = None, # mask, define with openems.Dielectric() 21 | endmetal = True, # add metal to filter ends 22 | ): 23 | pec = openems.Metal(em, 'pec_filter') 24 | ring_y_width = via_padradius * 2.0 25 | ring_ix = 0.5 * (np.max(rl) + end_gap) 26 | ring_ox = ring_ix + 2.0 * via_padradius 27 | via_z = [[z[0], z[1]+lidz], [z[1], z[2]]] 28 | # fingers 29 | y = -0.5*space[-1:][0] 30 | mirror = np.array([1,1,1]) 31 | if len(space) % 2 == 0: 32 | mirror[0] *= -1 33 | for i in range(len(space))[::-1]: # reverse order 34 | x1 = 0.5 * (ring_ox + ring_ix) 35 | x2 = ring_ix - rl[i] 36 | y += space[i] 37 | start = [x1, y, z[1]]; 38 | y += rw[i] 39 | stop = [x2, y, z[2]]; 40 | for m in [np.array([1,1,1]),np.array([-1,-1,1])]: 41 | m *= mirror 42 | openems.Box(pec, 9, start*m, stop*m, padname = 'poly', pcb_layer=pcb_layer) 43 | openems.Via(pec, priority=2, 44 | x=m[0]*(ring_ix+via_padradius), 45 | y=m[1]*(y-0.5*rw[i]), 46 | z=via_z, 47 | drillradius=via_radius, 48 | padradius=via_padradius, padname='2') 49 | mirror[0] *= -1 50 | 51 | if endmetal: 52 | v.duplicate().mirror('y') 53 | v.duplicate().mirror('x') 54 | 55 | mirror *= [-1,1,1] 56 | # ports 57 | y1 = y 58 | y -= 0.5*rw[0] # center of line 59 | y2 = y + 0.5*feedwidth + ring_y_width # y outside 60 | px = ring_ix - tapoffset 61 | py = y2 - portlength 62 | for m in [-1,1]: 63 | start = [m*(px + 0.5*feedwidth), m*y2, z[1]] 64 | stop = [m*(px - 0.5*feedwidth), m*py, z[2]] 65 | openems.Port(em, start, stop, direction='y', z=50) 66 | # feed lines 67 | start = [m*(px + 0.5*feedwidth), m*py, z[1]] 68 | stop = [m*(px - 0.5*feedwidth), m*(y-0.5*rw[0]), z[2]] 69 | openems.Box(pec, 9, start, stop, padname = '1' if m==1 else '3', 70 | pcb_layer=pcb_layer) 71 | # substrate 72 | start = np.array([ring_ox, y2, z[0]]) 73 | stop = openems.mirror(start, 'xy') 74 | stop[2] = z[1]+lidz 75 | sub = openems.Box(sub, 1, start, stop) 76 | # mask 77 | if mask_thickness > 0.0: 78 | start = np.array([ring_ox, y2, z[1]]) 79 | stop = openems.mirror(start, 'xy') 80 | stop[2] += mask_thickness 81 | openems.Box(mask, 1, start, stop) 82 | # grounded end metal 83 | if endmetal: 84 | for xm in [-1,1]: 85 | em1 = openems.Box(pec, 9, start = [xm*ring_ix, y2, z[1]], 86 | stop = [xm*(ring_ix + 2.0*via_padradius), -y2, z[2]], 87 | padname = '2', pcb_layer=pcb_layer) 88 | -------------------------------------------------------------------------------- /kicadfpwriter.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2005-2019 Darrell Harmon, David Carr, Peter Baxendale, Stephen Ecob, Larry Doolittle 2 | # GPLv3+ 3 | 4 | class Generator(): 5 | def __init__(self, part): 6 | self.mirror = "" 7 | self.fp = "(module {} (layer F.Cu)\n".format(part) 8 | self.fp += " (at 0 0)\n" 9 | self.fp += " (attr smd)" 10 | self.fp += " (fp_text reference U1 (at 0 -1.27) (layer F.SilkS) hide (effects (font (size 0.7 0.7) (thickness 0.127))))\n" 11 | self.fp += " (fp_text value value (at 0 1.27) (layer F.SilkS) hide (effects (font (size 0.7 0.7) (thickness 0.127))))\n" 12 | 13 | # mm, degrees 14 | def add_pad(self, 15 | name, 16 | x, 17 | y, 18 | xsize=None, 19 | ysize=None, 20 | diameter=None, 21 | drill = 0, 22 | layer='F.Cu', 23 | mirror = "", 24 | shape="rect"): 25 | if "x" in self.mirror: 26 | x *= -1.0 27 | if "y" in self.mirror: 28 | y *= -1.0 29 | if "cir" in shape: 30 | shape = "circle" 31 | xsize = diameter 32 | ysize = diameter 33 | if "round" in shape: 34 | shape = "oval" 35 | atstring = "(at {:.6f} {:.6f})".format(x, y) 36 | padtype = "smd" 37 | layers = " (layers {})".format(layer) 38 | drillstring = "" 39 | if drill > 0: 40 | drillstring = " (drill {:.6f})".format(drill) 41 | padtype = "thru_hole" 42 | layers = " (layers *.Cu)" 43 | self.fp += " (pad {} {} {} {} (size {:.6f} {:.6f}){}".format( 44 | name, padtype, shape, atstring, xsize, ysize, drillstring) 45 | self.fp += layers 46 | self.fp += ")\n" 47 | 48 | def add_polygon(self, points, layer="F.Cu", width = 0.0): 49 | polystring = "(fp_poly (pts\n" 50 | for p in points: 51 | if "x" in self.mirror: 52 | p[0] *= -1.0 53 | if "y" in self.mirror: 54 | p[1] *= -1.0 55 | polystring += "\t(xy {:.6f} {:.6f})\n".format(p[0], p[1]) 56 | polystring += ") (layer {}) (width {:.6f}) )\n".format(layer, width) 57 | self.fp += polystring 58 | 59 | def add_custom_pad(self, name, x, y, polygons, layer="F.Cu"): 60 | self.fp += "(pad {} connect custom (at {} {}) (size 0.1 0.1) (layers {})\n".format( 61 | name, x, y, layer) 62 | self.fp += "(options (clearance outline) (anchor circle))\n" 63 | self.fp += "(primitives\n" 64 | for polygon in polygons: 65 | polygon -= [x,y] 66 | self.fp += "(gr_poly (pts\n" 67 | for p in polygon: 68 | if "x" in self.mirror: 69 | p[0] *= -1.0 70 | if "y" in self.mirror: 71 | p[1] *= -1.0 72 | self.fp += "\t(xy {:.6f} {:.6f})\n".format(p[0], p[1]) 73 | self.fp += ") (width 0))\n" 74 | self.fp += "))\n" 75 | 76 | def finish(self): 77 | self.fp += ")\n" 78 | return self.fp 79 | -------------------------------------------------------------------------------- /miter.py: -------------------------------------------------------------------------------- 1 | from scipy.constants import pi, c, epsilon_0, mu_0, mil, inch 2 | mm = 0.001 3 | import openems 4 | import numpy as np 5 | 6 | def generate(em, metal, substrate, miter, z, port_length, ms_width, box_size, priority = 9): 7 | d1 = 0.5 * box_size 8 | d2 = d1 - port_length 9 | d3 = d2 - 0.2*mm 10 | d4 = -0.5*ms_width + miter 11 | 12 | # substrate 13 | start = np.array([ 0.5*box_size, 0.5*box_size, z[0]]) 14 | stop = np.array([-0.5*box_size, -0.5*box_size, z[1]]) 15 | openems.Box(substrate, 1, start, stop); 16 | 17 | # port pads 18 | start = np.array([ 0.5*ms_width, d2, z[1]]) 19 | stop = np.array([-0.5*ms_width, d3, z[2]]) 20 | openems.Box(metal, priority, start, stop, padname = '1') 21 | start = np.array([d2, 0.5*ms_width, z[1]]) 22 | stop = np.array([d3, -0.5*ms_width, z[2]]) 23 | openems.Box(metal, priority, start, stop, padname = '2') 24 | 25 | # line 26 | openems.Polygon(metal, 27 | priority = priority, 28 | points = np.array([[-0.5*ms_width, d2], 29 | [ 0.5*ms_width, d2], 30 | [ 0.5*ms_width, 0.5*ms_width], 31 | [ d2, 0.5*ms_width], 32 | [ d2, -0.5*ms_width], 33 | [ d4, -0.5*ms_width], 34 | [ -0.5*ms_width, d4]]), 35 | elevation = z[1:], 36 | normal_direction = 'z', 37 | pcb_layer = 'F.Cu', 38 | pcb_width = 0.001) 39 | 40 | # ports 41 | start = np.array([ 0.5*ms_width, d1, z[1]]) 42 | stop = np.array([-0.5*ms_width, d2, z[2]]) 43 | openems.Port(em, start, stop, direction='y', z=50) 44 | start = np.array([d1, 0.5*ms_width, z[1]]) 45 | stop = np.array([d2, -0.5*ms_width, z[2]]) 46 | openems.Port(em, start, stop, direction='x', z=50) 47 | -------------------------------------------------------------------------------- /polygon.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import openems 3 | 4 | class Polygon(openems.Object): 5 | def __init__(self, 6 | material, 7 | points, 8 | elevation, 9 | priority=1, 10 | normal_direction = 'z', 11 | pcb_layer = 'F.Cu', 12 | mirror = '', 13 | **kwargs 14 | ): 15 | """ 16 | Add a polygon, generally assumed to be in xy plane, but can be changed to yz or xz 17 | name: any unique name 18 | material: the name of a previously defined material 19 | priority: in the case of overlapping materials, the one with higher priority will be used 20 | points: pairs of points (xy, yz or xz) [[x1, y1], [x2, y2], ...] 21 | elevation: start and stop points in the normal direction 22 | normal_direction: optional, default = z, direction normal to the polygon - 'x', 'y' or 'z' 23 | """ 24 | self.points = np.array(points) 25 | if mirror != '': 26 | assert(normal_direction == 'z') 27 | if 'x' in mirror: 28 | self.points[:,0] *= -1.0 29 | if 'y' in mirror: 30 | self.points[:,1] *= -1.0 31 | self.priority = priority 32 | self.material = material 33 | self.elevation = elevation 34 | self.pcb_layer = pcb_layer 35 | self.normal_direction = normal_direction 36 | self.em = material.em 37 | name = self.em.get_name(None) 38 | self.em.objects[name] = self 39 | self.kwargs = kwargs 40 | 41 | def generate_kicad(self, g): 42 | if self.material.__class__.__name__ == 'Dielectric': 43 | return 44 | if self.pcb_layer == None: 45 | return 46 | if 'is_custom_pad' in self.kwargs.keys(): 47 | name = self.kwargs["pad_name"] 48 | x = self.kwargs["x"]*1000.0 49 | y = self.kwargs["y"]*1000.0 50 | g.add_custom_pad(name, x, y, [1000.0*self.points], layer=self.pcb_layer) 51 | else: 52 | g.add_polygon(points = 1000.0 * self.points, layer = self.pcb_layer, width=0) 53 | 54 | def generate_octave(self): 55 | height = self.elevation[1] - self.elevation[0] 56 | self.material.material.AddLinPoly(np.swapaxes(self.points, 0, 1), 57 | self.normal_direction, 58 | self.elevation[0], 59 | height, 60 | priority=self.priority) 61 | -------------------------------------------------------------------------------- /ratrace_folded.py: -------------------------------------------------------------------------------- 1 | mm = 0.001 2 | import openems 3 | import numpy as np 4 | pi = np.pi 5 | cat = np.concatenate 6 | arc = openems.arc 7 | 8 | def ratrace( 9 | metal, substrate, 10 | z, # [bottom of substrate, top of substrate, top of metal] 11 | port_length, 12 | ms_width, priority = 9, 13 | lq=8.9*mm, w=0.175*mm, r1=0.7*mm, r2 = 0.7*mm, kicad=False, loopr=1.0*mm): 14 | em = metal.em 15 | ms_width = ms_width 16 | endspace = 0.5 * mm 17 | 18 | feedx = -lq + -r2 + pi*r2/2.0 19 | arcx = feedx - lq + (r1+r2)*pi/2 20 | endspace = ms_width + port_length if kicad else endspace 21 | 22 | def addy_sym(y): 23 | em.mesh.AddLine('y',y) 24 | em.mesh.AddLine('y',-y) 25 | 26 | addy_sym(r1 - 0.5*w) 27 | addy_sym(r1 + 0.5*w) 28 | addy_sym(r1+r2 - 0.5*w) 29 | addy_sym(r1+r2 + 0.5*w) 30 | 31 | # substrate 32 | start = np.array([endspace, r1+r2+endspace, z[0]]) 33 | stop = np.array([arcx - r1 - r2 - endspace, -1.0*start[1], z[1]]) 34 | substrate.AddBox(start, stop, 1) 35 | 36 | # outer loop 37 | c1 = arcx 38 | lo = cat(( 39 | arc(-1.0*r2, r1, r2+0.5*w, 0*pi, 0.5*pi), 40 | arc(c1, 0, r1+r2+0.5*w, 0.5*pi, 1.5*pi), 41 | arc(-1.0*r2, -r1, r2+0.5*w, 1.5*pi, 2*pi), 42 | )) 43 | li = cat(( 44 | arc(-1.0*r2, r1, r2-0.5*w, 0*pi, 0.5*pi), 45 | arc(c1, 0, r1+r2-0.5*w, 0.5*pi, 1.5*pi), 46 | arc(-1.0*r2, -r1, r2-0.5*w, 1.5*pi, 2*pi), 47 | )) 48 | l = cat((lo, li[::-1])) 49 | metal.AddPolygon( 50 | priority = priority, 51 | points = l, 52 | elevation = z[1:], 53 | normal_direction = 'z', 54 | pcb_layer = 'F.Cu', 55 | pcb_width = 0.001) 56 | 57 | # inner loop 58 | lo = arc(np.pi*r1/2.0 - lq, 0, r1+0.5*w, 0.5*pi, 1.5*pi) 59 | li = arc(np.pi*r1/2.0 - lq, 0, r1-0.5*w, 0.5*pi, 1.5*pi) 60 | lo = cat((lo, [[0.5*w, -(r1+0.5*w)], [0.5*w, -(r1-0.5*w)]])) 61 | li = cat(([[0.5*w, r1+0.5*w], [0.5*w, r1-0.5*w]], li)) 62 | l = cat((lo, li[::-1])) 63 | metal.AddPolygon( 64 | priority = priority, 65 | points = l, 66 | elevation = z[1:], 67 | normal_direction = 'z', 68 | pcb_layer = 'F.Cu', 69 | pcb_width = 0.001) 70 | 71 | # delta port 72 | start = [feedx + 0.5*ms_width, -1.0*(r1+r2)-endspace, z[1]] 73 | stop = [start[0] - ms_width, start[1] + port_length, z[2]] 74 | openems.Port(em, start, stop, direction='y', z=50) 75 | 76 | # delta line 77 | start[1] = -1.0*(r1+r2) 78 | metal.AddBox(start, stop, priority, padname = '1') 79 | 80 | # 0/180 ports 81 | start = [endspace, r1-0.5*ms_width, z[1]] 82 | stop = [endspace - port_length, r1+0.5*ms_width, z[2]] 83 | for m in ['', 'y']: 84 | openems.Port(em, start, stop, direction='x', z=50, mirror = m) 85 | 86 | # 0/180 lines 87 | start[0] = 0 88 | metal.AddBox(start, stop, priority, padname = '3') 89 | metal.AddBox(start, stop, priority, padname = '4', mirror='y') 90 | 91 | # sum port 92 | portx1 = feedx - 0.5*mm 93 | portx2 = portx1 - port_length 94 | start = [portx1, -0.5*ms_width, z[1]] 95 | stop = [portx2, 0.5*ms_width, z[2]] 96 | openems.Port(em, start, stop, direction='x', z=50) 97 | 98 | # sum line 99 | start[0] = feedx 100 | stop[0] = portx1 101 | metal.AddBox(start, stop, priority, padname = '2') 102 | 103 | # sum ground 104 | start[0] = portx2 105 | stop[0] = portx2 - 0.5*mm 106 | start[2] = 0 107 | if not kicad: 108 | metal.AddBox(start, stop, priority) 109 | 110 | # ground loops 111 | if kicad: 112 | r = loopr 113 | y1 = r1+r 114 | x = -lq + pi*r 115 | w = ms_width 116 | l = cat(( 117 | arc(0, y1, r+0.5*w, -0.5*pi, 0.5*pi), 118 | [[x, y1+r+0.5*w], [x, y1+r-0.5*w]], 119 | arc(0, y1, r-0.5*w, 0.5*pi, -0.5*pi), 120 | )) 121 | for m in ['', 'y']: 122 | metal.AddPolygon( 123 | priority = priority, 124 | points = l, 125 | elevation = z[1:], 126 | normal_direction = 'z', 127 | pcb_layer = 'F.Cu', 128 | pcb_width = 0.001, 129 | mirror=m 130 | ) 131 | openems.Via(metal, priority=2, x=x, y=y1+r, z=[[0, z[2]], [z[1], z[2]]], 132 | drillradius=0.1*mm, 133 | padradius=0.225*mm, padname='5', 134 | mirror=m) 135 | -------------------------------------------------------------------------------- /wilkinson.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | mm = 0.001 3 | import openems 4 | import numpy as np 5 | import math 6 | 7 | pi = math.pi 8 | cat = np.concatenate 9 | arc = openems.arc 10 | 11 | def generate(em, 12 | substrate, 13 | z, # [bottom of substrate, top of substrate, top of metal] 14 | y1 = 5.5*mm, 15 | y2 = 0.9*mm, 16 | r = 0.5 * mm, 17 | rv = [200, 200, 200, 200], 18 | w = [0.127*mm, 0.152*mm, 0.19*mm, 0.23*mm], 19 | port_length = 0.2*mm, 20 | ms_width = 0.36*mm, 21 | endspace = 0.5 * mm, 22 | resistor_size='0402' 23 | ): 24 | alumina = openems.Dielectric(em, 'alumina', eps_r=9.8) 25 | metal = openems.Metal(em, 'pec_wilkinson') 26 | n = len(rv) 27 | 28 | # substrate 29 | start = np.array([(n*4 - 1)*r+endspace, y1+r+endspace, z[0]]) 30 | stop = np.array([-1.0*endspace, -1.0*start[1], z[1]]) 31 | substrate.AddBox(start, stop, 1) 32 | 33 | # common port line (pad 1) 34 | start = np.array([-1.0*endspace + port_length, 0.5*ms_width, z[1]]) 35 | stop = np.array([0, -0.5*ms_width, z[2]]) 36 | metal.AddBox(start, stop, priority=9, padname = '1') 37 | 38 | lo = arc(r, -y1, r+0.5*w[0], pi, 2*pi, 16) 39 | li = arc(r, -y1, r-0.5*w[0], pi, 2*pi, 16) 40 | lo = cat((lo, arc(3*r, -y2, r-0.5*w[0], pi, 0.55*pi, 16))) 41 | li = cat((li, arc(3*r, -y2, r+0.5*w[0], pi, 0.55*pi, 16))) 42 | for i in range(n-1): 43 | x = (4*i+3)*r 44 | w2 = w[1+i] 45 | em.mesh.AddLine('x', x + r - 0.5*w2) 46 | em.mesh.AddLine('x', x + r + 0.5*w2) 47 | em.mesh.AddLine('x', x + 3*r - 0.5*w2) 48 | em.mesh.AddLine('x', x + 3*r + 0.5*w2) 49 | lo = cat((lo, arc(x, -y2, r-0.5*w2, 0.45*pi, 0, 16))) 50 | li = cat((li, arc(x, -y2, r+0.5*w2, 0.45*pi, 0, 16))) 51 | lo = cat((lo, arc(2*r + x, -y1, r+0.5*w2, pi, 2*pi, 16))) 52 | li = cat((li, arc(2*r + x, -y1, r-0.5*w2, pi, 2*pi, 16))) 53 | lo = cat((lo, arc(4*r + x, -y2, r-0.5*w2, pi, 0.55*pi, 16))) 54 | li = cat((li, arc(4*r + x, -y2, r+0.5*w2, pi, 0.55*pi, 16))) 55 | l = cat((lo, li[::-1])) 56 | l = cat((l[::-1]*[1,-1], l)) 57 | openems.Polygon(metal, 58 | priority = 9, 59 | points = l, 60 | elevation = z[1:], 61 | normal_direction = 'z', 62 | pcb_layer = 'F.Cu', 63 | pcb_width = 0.001) 64 | 65 | # x at 2 port side 66 | x0 = (n*4 - 1) * r + endspace 67 | x1 = x0 - port_length 68 | 69 | # output lines 70 | for (ym,padname) in [(1,2),(-1,3)]: 71 | start = np.array([x1, ym*0.2*mm, z[1]]) 72 | stop = np.array([(n*4 - 1)*r-0.1*mm, ym*(0.2*mm+ms_width), z[2]]) 73 | lp2 = metal.AddBox(start, stop, priority=9, padname = padname) 74 | 75 | # main line port 76 | start = [-1.0*endspace, -0.5*ms_width, z[1]] 77 | stop = [-1.0*endspace + port_length, 0.5*ms_width, z[2]] 78 | openems.Port(em, start, stop, direction='x', z=50) 79 | 80 | # coupled line ports 81 | for ym in [-1,1]: 82 | start = [x0 - port_length, ym*0.2*mm, z[1]] 83 | stop = [x0, ym*(0.2*mm+ms_width), z[2]] 84 | openems.Port(em, start, stop, direction='x', z=50) 85 | 86 | for i in range(n): 87 | if not rv[i]: 88 | continue 89 | em.add_resistor('r{}'.format(i), 90 | origin=np.array([4*r*i + 3*r,0,z[2]]), 91 | direction='y', 92 | value=rv[i], invert=False, priority=9, dielectric=alumina, 93 | metal=metal, 94 | element_down=False, 95 | size = resistor_size) 96 | --------------------------------------------------------------------------------