├── .gitignore ├── HISTORY.txt ├── LICENSE.txt ├── MANIFEST.in ├── README.rst ├── cx_setup.py ├── docs ├── Makefile ├── _static │ └── .empty ├── _templates │ └── .empty ├── api.rst ├── conf.py ├── example.py ├── example_tables.py ├── gettingstarted.rst ├── index.rst ├── make.bat └── usage.rst ├── examples ├── __init__.py ├── cable_list_from_schemes.py ├── cable_tables_to_csv.py ├── cables_xls_to_autocad.py ├── calc_cb_place_consumption.py ├── dev_get_table_info.py ├── get_names.py └── lights.py ├── hello_world.py ├── pyautocad ├── __init__.py ├── api.py ├── cache.py ├── compat.py ├── contrib │ ├── __init__.py │ └── tables.py ├── types.py └── utils.py ├── setup.py └── tests ├── __init__.py ├── cmd_with_path.bat ├── test_api.py ├── test_cached.py ├── test_contrib_tables.py ├── test_some_ideas.py ├── test_types.py └── test_utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | *.idea 3 | build 4 | release 5 | _build 6 | dist 7 | MANIFEST 8 | -------------------------------------------------------------------------------- /HISTORY.txt: -------------------------------------------------------------------------------- 1 | History 2 | ------- 3 | 4 | 0.2.0 (2015-12-21) 5 | +++++++++++++++++++ 6 | 7 | * Experimental Python 3 support 8 | 9 | 10 | 0.1.2 (2012-03-31) 11 | +++++++++++++++++++ 12 | 13 | * Documentation improvements 14 | * ``cache.Cached`` proxy for caching expensive object attributes 15 | * ``utils.suppressed_regeneration_of(table)`` context manager 16 | * Fix: `cx_setup.py` script exclude list 17 | 18 | 19 | 0.1.1 (2012-03-25) 20 | +++++++++++++++++++ 21 | 22 | * Documentation and usage examples 23 | 24 | 0.1.0 (2012-03-23) 25 | +++++++++++++++++++ 26 | 27 | * initial PyPI release 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012, Roman Haritonov 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the distribution. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 19 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE.txt AUTHORS README.rst HISTORY.txt 2 | include *.py 3 | recursive-include tests *.py 4 | recursive-include examples *.py 5 | recursive-include docs * 6 | recursive-exclude docs *.pyc 7 | recursive-exclude docs *.pyo 8 | prune docs/_build 9 | prune docs/_themes/.git 10 | 11 | 12 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pyautoacad - AutoCAD Automation for Python 2 | ------------------------------------------ 3 | 4 | This library aimed to simplify writing ActiveX_ Automation_ scripts for AutoCAD_ with Python 5 | 6 | Requires: 7 | ---------- 8 | 9 | - comtypes_ 10 | 11 | 12 | Optional: 13 | 14 | - xlrd_, tablib_ 15 | 16 | 17 | Features: 18 | ----------- 19 | 20 | - Simplifies work with coordinates (3D points) 21 | - Efficient objects iteration and searching (with casting to correct type) 22 | - Excel/csv/json import and export (xlrd_ and tablib_ required) 23 | 24 | Simple usage example: 25 | --------------------- 26 | 27 | .. code-block:: python 28 | 29 | from pyautocad import Autocad, APoint 30 | 31 | 32 | acad = Autocad() 33 | acad.prompt("Hello, Autocad from Python\n") 34 | print acad.doc.Name 35 | 36 | p1 = APoint(0, 0) 37 | p2 = APoint(50, 25) 38 | for i in range(5): 39 | text = acad.model.AddText('Hi %s!' % i, p1, 2.5) 40 | acad.model.AddLine(p1, p2) 41 | acad.model.AddCircle(p1, 10) 42 | p1.y += 10 43 | 44 | dp = APoint(10, 0) 45 | for text in acad.iter_objects('Text'): 46 | print('text: %s at: %s' % (text.TextString, text.InsertionPoint)) 47 | text.InsertionPoint = APoint(text.InsertionPoint) + dp 48 | 49 | for obj in acad.iter_objects(['Circle', 'Line']): 50 | print(obj.ObjectName) 51 | 52 | See more examples_ in source distribution. 53 | 54 | Links 55 | ----- 56 | 57 | - **Documentation** at `readthedocs.org `_ 58 | 59 | - **Source code and issue tracking** at `GitHub `_. 60 | 61 | .. _ActiveX: http://wikipedia.org/wiki/ActiveX 62 | .. _Automation: http://en.wikipedia.org/wiki/OLE_Automation 63 | .. _AutoCAD: http://wikipedia.org/wiki/AutoCAD 64 | .. _comtypes: http://pypi.python.org/pypi/comtypes 65 | .. _xlrd: http://pypi.python.org/pypi/xlrd 66 | .. _tablib: http://pypi.python.org/pypi/tablib 67 | .. _examples: https://github.com/reclosedev/pyautocad/tree/master/examples 68 | .. _documentation: https://pyautocad.readthedocs.io/ 69 | -------------------------------------------------------------------------------- /cx_setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | cx_setup 5 | ~~~~~~~~ 6 | 7 | Converts example scripts to Windows executables. 8 | """ 9 | import glob 10 | import sys 11 | 12 | from cx_Freeze import setup, Executable 13 | 14 | if not len(sys.argv[1:]): 15 | sys.argv.append('install_exe') 16 | 17 | install_exe_options = {'install_dir': './Autocad tools'} 18 | build_exe_options = {'excludes': ['bz2', '_hashlib', 'unittest', 'tests']} 19 | 20 | 21 | exclude_scripts = [r'examples\__init__.py', r'examples\dev_get_table_info.py'] 22 | scripts_to_build = [name for name in glob.glob('examples/*.py') if 23 | name not in exclude_scripts] 24 | 25 | 26 | setup(name="Autocad tools", 27 | version="0.1", 28 | description="Generate cable list, get drawing names etc.", 29 | options=dict(install_exe=install_exe_options, 30 | build_exe=build_exe_options), 31 | executables=[Executable(script=script) for script in scripts_to_build]) -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyautocad.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyautocad.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/pyautocad" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyautocad" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /docs/_static/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reclosedev/pyautocad/527a672099b1b3b95e055fcbbbb3b67e305f0fe5/docs/_static/.empty -------------------------------------------------------------------------------- /docs/_templates/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reclosedev/pyautocad/527a672099b1b3b95e055fcbbbb3b67e305f0fe5/docs/_templates/.empty -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API 2 | === 3 | 4 | This part of the documentation covers all the interfaces of `pyautocad` 5 | 6 | ``api`` - Main Autocad interface 7 | -------------------------------------------- 8 | 9 | .. automodule:: pyautocad.api 10 | :members: 11 | 12 | .. data:: ACAD 13 | 14 | Constants from AutoCAD type library, for example:: 15 | 16 | text.Alignment = ACAD.acAlignmentRight 17 | 18 | ----------------------------------------------------------------------------- 19 | 20 | ``types`` - 3D Point and other AutoCAD data types 21 | ------------------------------------------------- 22 | 23 | .. automodule:: pyautocad.types 24 | :members: 25 | 26 | ----------------------------------------------------------------------------- 27 | 28 | ``utils`` - Utility functions 29 | ------------------------------------------------------------- 30 | 31 | .. automodule:: pyautocad.utils 32 | :members: 33 | :exclude-members: timing, suppressed_regeneration_of 34 | 35 | .. autofunction:: timing(message) 36 | .. autofunction:: suppressed_regeneration_of(table) 37 | 38 | 39 | 40 | 41 | 42 | ----------------------------------------------------------------------------- 43 | 44 | ``contrib.tables`` - Import and export tabular data from popular formats 45 | ------------------------------------------------------------------------ 46 | 47 | .. automodule:: pyautocad.contrib.tables 48 | :synopsis: test 49 | :members: 50 | 51 | ----------------------------------------------------------------------------- 52 | 53 | ``cache`` - Cache all object's attributes 54 | ------------------------------------------ 55 | .. versionadded:: 0.1.2 56 | 57 | .. automodule:: pyautocad.cache 58 | :members: -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # pyautocad documentation build configuration file, created by 4 | # sphinx-quickstart on Sun Mar 25 09:29:19 2012. 5 | # 6 | # This file is execfile()d with the current directory set to its containing dir. 7 | # 8 | # Note that not all possible configuration values are present in this 9 | # autogenerated file. 10 | # 11 | # All configuration values have a default; values that are commented out 12 | # serve to show the default. 13 | 14 | import sys, os 15 | on_rtd = os.environ.get('READTHEDOCS', None) == 'True' 16 | # If extensions (or modules to document with autodoc) are in another directory, 17 | # add these directories to sys.path here. If the directory is relative to the 18 | # documentation root, use os.path.abspath to make it absolute, like shown here. 19 | sys.path.insert(0, os.path.abspath('..')) 20 | 21 | class Mock(object): 22 | def __init__(self, *args, **kwargs): 23 | pass 24 | 25 | def __call__(self, *args, **kwargs): 26 | return Mock() 27 | 28 | @classmethod 29 | def __getattr__(self, name): 30 | if name in ('__file__', '__path__'): 31 | return os.devnull 32 | elif name[0] == name[0].upper(): 33 | return type(name, (), {}) 34 | else: 35 | return Mock() 36 | 37 | MOCK_MODULES = ['comtypes', 'tablib', 'xlrd'] 38 | for mod_name in MOCK_MODULES: 39 | sys.modules[mod_name] = Mock() 40 | 41 | from pyautocad import __version__ 42 | 43 | # -- General configuration ----------------------------------------------------- 44 | 45 | # If your documentation needs a minimal Sphinx version, state it here. 46 | #needs_sphinx = '1.0' 47 | 48 | # Add any Sphinx extension module names here, as strings. They can be extensions 49 | # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 50 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] 51 | 52 | # Add any paths that contain templates here, relative to this directory. 53 | templates_path = ['_templates'] 54 | 55 | # The suffix of source filenames. 56 | source_suffix = '.rst' 57 | 58 | # The encoding of source files. 59 | #source_encoding = 'utf-8-sig' 60 | 61 | # The master toctree document. 62 | master_doc = 'index' 63 | 64 | # General information about the project. 65 | project = u'pyautocad' 66 | copyright = u'2014, Roman Haritonov' 67 | 68 | # The version info for the project you're documenting, acts as replacement for 69 | # |version| and |release|, also used in various other places throughout the 70 | # built documents. 71 | # 72 | # The short X.Y version. 73 | version = __version__ 74 | # The full version, including alpha/beta/rc tags. 75 | release = __version__ 76 | 77 | # The language for content autogenerated by Sphinx. Refer to documentation 78 | # for a list of supported languages. 79 | #language = None 80 | 81 | # There are two options for replacing |today|: either, you set today to some 82 | # non-false value, then it is used: 83 | #today = '' 84 | # Else, today_fmt is used as the format for a strftime call. 85 | #today_fmt = '%B %d, %Y' 86 | 87 | # List of patterns, relative to source directory, that match files and 88 | # directories to ignore when looking for source files. 89 | exclude_patterns = ['_build'] 90 | 91 | # The reST default role (used for this markup: `text`) to use for all documents. 92 | #default_role = None 93 | 94 | # If true, '()' will be appended to :func: etc. cross-reference text. 95 | #add_function_parentheses = True 96 | 97 | # If true, the current module name will be prepended to all description 98 | # unit titles (such as .. function::). 99 | #add_module_names = True 100 | 101 | # If true, sectionauthor and moduleauthor directives will be shown in the 102 | # output. They are ignored by default. 103 | #show_authors = False 104 | 105 | # The name of the Pygments (syntax highlighting) style to use. 106 | pygments_style = 'sphinx' 107 | 108 | # A list of ignored prefixes for module index sorting. 109 | #modindex_common_prefix = [] 110 | 111 | 112 | # -- Options for HTML output --------------------------------------------------- 113 | 114 | # The theme to use for HTML and HTML Help pages. See the documentation for 115 | # a list of builtin themes. 116 | if on_rtd: 117 | html_theme = 'default' 118 | else: 119 | html_theme = 'default' 120 | 121 | # Theme options are theme-specific and customize the look and feel of a theme 122 | # further. For a list of options available for each theme, see the 123 | # documentation. 124 | #html_theme_options = {} 125 | 126 | # Add any paths that contain custom themes here, relative to this directory. 127 | #html_theme_path = [] 128 | 129 | # The name for this set of Sphinx documents. If None, it defaults to 130 | # " v documentation". 131 | #html_title = None 132 | 133 | # A shorter title for the navigation bar. Default is the same as html_title. 134 | #html_short_title = None 135 | 136 | # The name of an image file (relative to this directory) to place at the top 137 | # of the sidebar. 138 | #html_logo = None 139 | 140 | # The name of an image file (within the static path) to use as favicon of the 141 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 142 | # pixels large. 143 | #html_favicon = None 144 | 145 | # Add any paths that contain custom static files (such as style sheets) here, 146 | # relative to this directory. They are copied after the builtin static files, 147 | # so a file named "default.css" will overwrite the builtin "default.css". 148 | html_static_path = ['_static'] 149 | 150 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 151 | # using the given strftime format. 152 | #html_last_updated_fmt = '%b %d, %Y' 153 | 154 | # If true, SmartyPants will be used to convert quotes and dashes to 155 | # typographically correct entities. 156 | #html_use_smartypants = True 157 | 158 | # Custom sidebar templates, maps document names to template names. 159 | #html_sidebars = {} 160 | 161 | # Additional templates that should be rendered to pages, maps page names to 162 | # template names. 163 | #html_additional_pages = {} 164 | 165 | # If false, no module index is generated. 166 | #html_domain_indices = True 167 | 168 | # If false, no index is generated. 169 | #html_use_index = True 170 | 171 | # If true, the index is split into individual pages for each letter. 172 | #html_split_index = False 173 | 174 | # If true, links to the reST sources are added to the pages. 175 | #html_show_sourcelink = True 176 | 177 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 178 | #html_show_sphinx = True 179 | 180 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 181 | #html_show_copyright = True 182 | 183 | # If true, an OpenSearch description file will be output, and all pages will 184 | # contain a tag referring to it. The value of this option must be the 185 | # base URL from which the finished HTML is served. 186 | #html_use_opensearch = '' 187 | 188 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 189 | #html_file_suffix = None 190 | 191 | # Output file base name for HTML help builder. 192 | htmlhelp_basename = 'pyautocaddoc' 193 | 194 | 195 | # -- Options for LaTeX output -------------------------------------------------- 196 | 197 | latex_elements = { 198 | # The paper size ('letterpaper' or 'a4paper'). 199 | #'papersize': 'letterpaper', 200 | 201 | # The font size ('10pt', '11pt' or '12pt'). 202 | #'pointsize': '10pt', 203 | 204 | # Additional stuff for the LaTeX preamble. 205 | #'preamble': '', 206 | } 207 | 208 | # Grouping the document tree into LaTeX files. List of tuples 209 | # (source start file, target name, title, author, documentclass [howto/manual]). 210 | latex_documents = [ 211 | ('index', 'pyautocad.tex', u'pyautocad Documentation', 212 | u'Roman Haritonov', 'manual'), 213 | ] 214 | 215 | # The name of an image file (relative to this directory) to place at the top of 216 | # the title page. 217 | #latex_logo = None 218 | 219 | # For "manual" documents, if this is true, then toplevel headings are parts, 220 | # not chapters. 221 | #latex_use_parts = False 222 | 223 | # If true, show page references after internal links. 224 | #latex_show_pagerefs = False 225 | 226 | # If true, show URL addresses after external links. 227 | #latex_show_urls = False 228 | 229 | # Documents to append as an appendix to all manuals. 230 | #latex_appendices = [] 231 | 232 | # If false, no module index is generated. 233 | #latex_domain_indices = True 234 | 235 | 236 | # -- Options for manual page output -------------------------------------------- 237 | 238 | # One entry per manual page. List of tuples 239 | # (source start file, name, description, authors, manual section). 240 | man_pages = [ 241 | ('index', 'pyautocad', u'pyautocad Documentation', 242 | [u'Roman Haritonov'], 1) 243 | ] 244 | 245 | # If true, show URL addresses after external links. 246 | #man_show_urls = False 247 | 248 | 249 | # -- Options for Texinfo output ------------------------------------------------ 250 | 251 | # Grouping the document tree into Texinfo files. List of tuples 252 | # (source start file, target name, title, author, 253 | # dir menu entry, description, category) 254 | texinfo_documents = [ 255 | ('index', 'pyautocad', u'pyautocad Documentation', 256 | u'Roman Haritonov', 'pyautocad', 'One line description of project.', 257 | 'Miscellaneous'), 258 | ] 259 | 260 | # Documents to append as an appendix to all manuals. 261 | #texinfo_appendices = [] 262 | 263 | # If false, no module index is generated. 264 | #texinfo_domain_indices = True 265 | 266 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 267 | #texinfo_show_urls = 'footnote' 268 | 269 | autoclass_content = 'both' 270 | autodoc_member_order = 'bysource' 271 | 272 | def skip_modules_docstring(app, what, name, obj, options, lines): 273 | if what == 'module': 274 | del lines[:] 275 | 276 | def setup(app): 277 | app.connect('autodoc-process-docstring', skip_modules_docstring) 278 | -------------------------------------------------------------------------------- /docs/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from pyautocad import Autocad, APoint 4 | 5 | acad = Autocad(create_if_not_exists=True) 6 | acad.prompt("Hello, Autocad from Python\n") 7 | print acad.doc.Name 8 | 9 | p1 = APoint(0, 0) 10 | p2 = APoint(50, 25) 11 | for i in range(5): 12 | text = acad.model.AddText(u'Hi %s!' % i, p1, 2.5) 13 | acad.model.AddLine(p1, p2) 14 | acad.model.AddCircle(p1, 10) 15 | p1.y += 10 16 | 17 | for obj in acad.iter_objects(): 18 | print obj.ObjectName 19 | 20 | for text in acad.iter_objects('Text'): 21 | print text.TextString, text.InsertionPoint 22 | 23 | for obj in acad.iter_objects(['Text', 'Line']): 24 | print obj.ObjectName 25 | 26 | def text_contains_3(text_obj): 27 | return '3' in text_obj.TextString 28 | 29 | text = acad.find_one('Text', predicate=text_contains_3) 30 | print text.TextString 31 | 32 | from pyautocad import ACAD 33 | 34 | for text in acad.iter_objects('Text'): 35 | old_insertion_point = APoint(text.InsertionPoint) 36 | text.Alignment = ACAD.acAlignmentRight 37 | text.TextAlignmentPoint = old_insertion_point 38 | 39 | for line in acad.iter_objects('Line'): 40 | p1 = APoint(line.StartPoint) 41 | line.EndPoint = p1 - APoint(20, 0) -------------------------------------------------------------------------------- /docs/example_tables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from pyautocad import Autocad, APoint 4 | from pyautocad.contrib.tables import Table 5 | 6 | acad = Autocad() 7 | p1 = APoint(0, 0) 8 | for i in range(5): 9 | obj = acad.model.AddText(u'Hi %s!' % i, p1, 2.5) 10 | p1.y += 10 11 | 12 | table = Table() 13 | for obj in acad.iter_objects('Text'): 14 | x, y, z = obj.InsertionPoint 15 | table.writerow([obj.TextString, x, y, z]) 16 | table.save('data.xls', 'xls') 17 | 18 | data = Table.data_from_file('data.xls') 19 | 20 | -------------------------------------------------------------------------------- /docs/gettingstarted.rst: -------------------------------------------------------------------------------- 1 | Getting started 2 | =============== 3 | 4 | Installation 5 | ------------ 6 | 7 | If you have pip_ or easy_install_, you can just:: 8 | 9 | pip install --upgrade pyautocad 10 | 11 | or:: 12 | 13 | easy_install -U pyautocad 14 | 15 | Also, you can download Windows installer from PyPI pyautocad_ page. 16 | 17 | .. _pyautocad: http://pypi.python.org/pypi/pyautocad/ 18 | .. _pip: http://pypi.python.org/pypi/pip/ 19 | .. _easy_install: http://pypi.python.org/pypi/setuptools 20 | 21 | Requirements 22 | ------------ 23 | 24 | - comtypes_ 25 | .. note:: 26 | 27 | If you are using pip_ or easy_install_, then it will be installed automatically. 28 | Otherwise you should install comtypes_ package manually. 29 | 30 | - Optional: xlrd_ and tablib_ for working with tables 31 | 32 | .. _comtypes: http://pypi.python.org/pypi/comtypes/ 33 | .. _xlrd: http://pypi.python.org/pypi/xlrd 34 | .. _tablib: http://pypi.python.org/pypi/tablib 35 | 36 | Retrieving AutoCAD ActiveX documentation 37 | ---------------------------------------- 38 | 39 | A copy of the AutoCAD ActiveX guide and reference can be found in the ``help`` directory of your AutoCAD install. 40 | 41 | 42 | - ``acad_aag.chm`` - ActiveX and VBA Developer's Guide 43 | - ``acadauto.chm`` - ActiveX and VBA Reference 44 | 45 | Reference can also be found in ``C:\Program Files\Common Files\Autodesk Shared\acadauto.chm`` 46 | 47 | What's next? 48 | ------------ 49 | 50 | Read the :doc:`usage` section, or look for real applications in examples_ folder of source distribution. 51 | 52 | .. note:: 53 | 54 | Applications in examples_ are Russian engineering specific, but anyway 55 | I hope you'll find something interesting in that code. 56 | 57 | For more info on features see :doc:`api` documentation and sources_. 58 | 59 | .. _examples: https://github.com/reclosedev/pyautocad/tree/master/examples 60 | .. _sources: https://github.com/reclosedev/pyautocad 61 | 62 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyautocad documentation master file, created by 2 | sphinx-quickstart on Sun Mar 25 09:29:19 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to pyautocad's documentation! 7 | ===================================== 8 | 9 | pyautocad_ - library aimed to simplify writing ActiveX_ Automation_ scripts for AutoCAD_ with Python 10 | 11 | Contents: 12 | ========= 13 | 14 | .. toctree:: 15 | :maxdepth: 2 16 | 17 | gettingstarted 18 | usage 19 | api 20 | 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | 29 | .. _pyautocad: http://pypi.python.org/pypi/pyautocad/ 30 | .. _ActiveX: http://wikipedia.org/wiki/ActiveX 31 | .. _Automation: http://en.wikipedia.org/wiki/OLE_Automation 32 | .. _AutoCAD: http://wikipedia.org/wiki/AutoCAD 33 | 34 | 35 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=_build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . 10 | set I18NSPHINXOPTS=%SPHINXOPTS% . 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyautocad.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyautocad.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ===== 3 | 4 | Main interface and types 5 | ------------------------- 6 | 7 | .. currentmodule:: pyautocad.api 8 | 9 | 10 | For our first example, we will use :class:`Autocad` (main Automation object) and 11 | :class:`pyautocad.types.APoint` for operations with coordinates 12 | 13 | .. literalinclude:: example.py 14 | :lines: 3 15 | 16 | Let's create AutoCAD application or connect to already running application: 17 | 18 | .. literalinclude:: example.py 19 | :lines: 5-7 20 | 21 | To work with AutoCAD documents and objects we can use ActiveX interface, 22 | :class:`Autocad` (from pyautocad) contains some methods to simplify common Automation tasks, such as 23 | object iteration and searching, getting objects from user's selection, printing messages. 24 | 25 | There are shortcuts for current ``ActiveDocument`` - :attr:`Autocad.doc` 26 | and ``ActiveDocument.ModelSpace`` - :attr:`Autocad.model` 27 | 28 | Let's add some objects to document: 29 | 30 | .. literalinclude:: example.py 31 | :lines: 9-15 32 | 33 | Now our document contains some ``Texts``, ``Lines`` and ``Circles``, let's iterate them all: 34 | 35 | .. literalinclude:: example.py 36 | :lines: 17-18 37 | 38 | Wea can also iterate objects of concrete type: 39 | 40 | .. literalinclude:: example.py 41 | :lines: 20-21 42 | 43 | .. note:: 44 | 45 | Object name can be partial and case insensitive, e.g. 46 | ``acad.iter_objects('tex')`` will return ``AcDbText`` and ``AcDbMText`` objects 47 | 48 | Or multiple types: 49 | 50 | .. literalinclude:: example.py 51 | :lines: 23-24 52 | 53 | Also we can find first object with some conditions. 54 | For example, let's find first text item which contains ``3``: 55 | 56 | .. literalinclude:: example.py 57 | :lines: 26-30 58 | 59 | To modify objects in document, we need to find interesting objects and change its properties. 60 | Some properties are described with constants, e.g. text alignment. These constants can be accessed through 61 | :data:`ACAD`. Let's change all text objects text alignment: 62 | 63 | .. literalinclude:: example.py 64 | :lines: 32-37 65 | 66 | .. currentmodule:: pyautocad.types 67 | 68 | In previous code we have converted ``text.InsertionPoint`` to :class:`APoint` because 69 | we can't just use default ``tuple`` when setting another properties such as ``text.TextAlignmentPoint``. 70 | 71 | If wee need to change position of some object, we should use :class:`APoint`, 72 | for example let's change lines end position: 73 | 74 | .. literalinclude:: example.py 75 | :lines: 39-41 76 | 77 | 78 | Working with tables 79 | ------------------- 80 | 81 | .. note:: 82 | 83 | To work with tables, xlrd_ and tablib_ should be installed. 84 | 85 | .. _xlrd: http://pypi.python.org/pypi/xlrd 86 | .. _tablib: http://pypi.python.org/pypi/tablib 87 | 88 | .. currentmodule:: pyautocad.contrib.tables 89 | 90 | To simplify importing and exporting data there is :class:`Table` class exist. 91 | It allows you to read and write tabular data in popular formats: 92 | 93 | - csv 94 | - xls 95 | - xlsx (write only) 96 | - json 97 | 98 | Let's try to solve some basic task. We need to save text and position 99 | from all text objects to Excel file, and then load it back. 100 | 101 | First we need to add some objects to AutoCAD: 102 | 103 | .. literalinclude:: example_tables.py 104 | :lines: 3-10 105 | 106 | Now we can iterate this objects and save them to Excel table: 107 | 108 | .. literalinclude:: example_tables.py 109 | :lines: 12-16 110 | 111 | After saving this data to 'data.xls' and probably changing it with some table 112 | processor software (e.g. Microsoft Office Excel) we can retrieve our data from file: 113 | 114 | .. literalinclude:: example_tables.py 115 | :lines: 18 116 | 117 | ``data`` will contain:: 118 | 119 | [[u'Hi 0!', 0.0, 0.0, 0.0], 120 | [u'Hi 1!', 0.0, 10.0, 0.0], 121 | [u'Hi 2!', 0.0, 20.0, 0.0], 122 | [u'Hi 3!', 0.0, 30.0, 0.0], 123 | [u'Hi 4!', 0.0, 40.0, 0.0]] 124 | 125 | .. seealso:: 126 | 127 | Example of working with AutoCAD table objects 128 | at `examples/dev_get_table_info.py `_ 129 | 130 | Improve speed 131 | ------------- 132 | 133 | - ActiveX technology is quite slow. When you are accessing object attributes like 134 | position, text, etc, every time call is passed to AutoCAD. It can slowdown execution 135 | time. For example if you have program, which combines single line 136 | text based on its relative positions, you probably need to get each text position 137 | several times. To speed this up, you can cache objects attributes using the :class:`pyautocad.cache.Cached` proxy (see example in class documentation) 138 | 139 | - To improve speed of AutoCAD table manipulations, you can use ``Table.RegenerateTableSuppressed = True`` 140 | or handy context manager :func:`suppressed_regeneration_of(table) `:: 141 | 142 | table = acad.model.AddTable(pos, rows, columns, row_height, col_width) 143 | with suppressed_regeneration_of(table): 144 | table.SetAlignment(ACAD.acDataRow, ACAD.acMiddleCenter) 145 | for row in range(rows): 146 | for col in range(columns): 147 | table.SetText(row, col, '%s %s' % (row, col)) 148 | 149 | Utility functions 150 | ----------------- 151 | 152 | There is also some utility functions for work with AutoCAD text objects and more. 153 | See :mod:`pyautocad.utils` documentation. -------------------------------------------------------------------------------- /examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reclosedev/pyautocad/527a672099b1b3b95e055fcbbbb3b67e305f0fe5/examples/__init__.py -------------------------------------------------------------------------------- /examples/cable_list_from_schemes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from collections import OrderedDict, namedtuple 4 | import optparse 5 | import os 6 | import re 7 | import logging 8 | 9 | 10 | from pyautocad import Autocad 11 | from pyautocad.utils import unformat_mtext, timing 12 | from pyautocad.contrib.tables import Table, available_write_formats 13 | 14 | 15 | logging.basicConfig(level=logging.INFO, format='%(message)s') 16 | logger = logging.getLogger() 17 | logger.addHandler(logging.FileHandler('cables_from_schemes.log', 'w')) 18 | 19 | 20 | CableEntry = namedtuple('CableEntry', 'name, source, target, cable, section, length') 21 | 22 | 23 | def get_known_targets(filename): 24 | if not os.path.exists(filename): 25 | logger.warning("Can't find file with known targets: %s", filename) 26 | return {} 27 | targets = OrderedDict() 28 | data = Table.data_from_file(filename) 29 | for row in data: 30 | if len(row) < 3: 31 | continue 32 | targets[row[0]] = row[2] 33 | return targets 34 | 35 | 36 | def get_cables(acad, block, known_targets): 37 | patterns = [ur"""(?P.*?)-(?P
[\dxх,\(\)]+)\s+ 38 | (?P\d+)\s*[мm]\\P 39 | \s*(?P[^-]+)-(?P.+)\s*""", 40 | 41 | ur"""(?P.*?)-(?P.*?)\s*\\P 42 | \s*(?P.*?)-(?P
[\dxх,\(\)]+)\s+ 43 | (?P\d+)\s*[мm]"""] 44 | patterns = [re.compile(pat, re.VERBOSE) for pat in patterns] 45 | 46 | for text in acad.iter_objects("dbmtext", block): 47 | text = unformat_mtext(text.TextString) 48 | logger.info(text) 49 | m_cable = None 50 | for pattern in patterns: 51 | m_cable = pattern.match(text) 52 | if m_cable: 53 | break 54 | if not m_cable: 55 | continue 56 | 57 | logger.info("!!!%s\n", text) 58 | m = m_cable.groupdict() 59 | cable_name = "%s-%s" % (m['name'], m['source']) 60 | target = known_targets.get(cable_name, '') 61 | if not target: 62 | target = m['name'] 63 | yield CableEntry(cable_name, m['source'], target, 64 | m['cable'], m['section'], m['length']) 65 | 66 | 67 | def sort_by_correct_order(messed_order, correct_order): 68 | return [x for x in correct_order if x in messed_order] +\ 69 | [x for x in messed_order if x not in correct_order] 70 | 71 | 72 | def sort_cables_by_targets(cables, targets): 73 | presorted_cables = OrderedDict() 74 | for entry in sorted(cables, key=lambda x: (x.source, x.name)): 75 | presorted_cables[entry.name] = entry 76 | if not targets: 77 | return presorted_cables.itervalues() 78 | sorted_cable_names = sort_by_correct_order(presorted_cables, targets) 79 | return (presorted_cables[name] for name in sorted_cable_names) 80 | 81 | 82 | def main(): 83 | acad = Autocad() 84 | parser = optparse.OptionParser(usage=u'%prog [опции] [файл для результатов]') 85 | parser.add_option('-f', '--format', 86 | choices=available_write_formats(), dest='format', 87 | metavar='FORMAT', default='xls', 88 | help=u"Формат файла (%s) по умолчанию - %%default" % 89 | ', '.join(available_write_formats())) 90 | parser.add_option('-k', '--known', dest='known_targets', 91 | metavar='FILE', default='', action='store', 92 | help=u'Файл с заполненым полем "Конец" и очередностью ' 93 | u' записей. По умолчанию берется из существующего файла') 94 | parser.add_option('-q', '--quiet', action='callback', 95 | callback=lambda *x: logging.disable(logging.WARNING), 96 | help=u'"Тихий" режим (не печатать в консоль)') 97 | parser.add_option('-s', '--single', dest='single_doc', action='store_true', 98 | default=False, 99 | help=u'Собрать данные только из текущего документа ' 100 | u'(Собирает из всех по умолчанию)') 101 | parser.add_option('-c', '--no-known', dest='dont_use_known', 102 | action='store_true', default=False, 103 | help=u'Не использовать данные об очередности и поле "Конец"') 104 | options, args = parser.parse_args() 105 | 106 | output_file = args[0] if args else u"cables_from_%s.%s" % (acad.doc.Name, options.format) 107 | if not options.known_targets and not options.dont_use_known: 108 | options.known_targets = output_file 109 | known_targets = get_known_targets(options.known_targets) 110 | output_table = Table() 111 | if options.single_doc: 112 | documents = [acad.doc] 113 | else: 114 | documents = acad.app.Documents 115 | 116 | for doc in documents: 117 | try: 118 | cables = get_cables(acad, doc.Modelspace, known_targets) 119 | sorted_cables = sort_cables_by_targets(cables, known_targets) 120 | for row in sorted_cables: 121 | output_table.writerow([s for s in row]) 122 | except Exception: 123 | logger.exception('Error while processing %s', doc.Name) 124 | output_table.save(output_file, options.format) 125 | 126 | 127 | if __name__ == "__main__": 128 | with timing(): 129 | main() 130 | 131 | # TODO append to existent file option 132 | # TODO atomic write 133 | -------------------------------------------------------------------------------- /examples/cable_tables_to_csv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #-*- coding: utf-8 -*- 3 | import optparse 4 | 5 | from pyautocad import Autocad, utils 6 | from pyautocad.contrib.tables import Table, available_write_formats 7 | 8 | 9 | def iter_cable_tables(acad, block): 10 | for table in acad.iter_objects("table", block): 11 | if table.Columns != 9: 12 | continue 13 | ncols = table.Columns # store in local, Automation is expensive 14 | for row in xrange(3, table.Rows): 15 | yield [utils.mtext_to_string(table.GetText(row, col)) 16 | for col in xrange(ncols)] 17 | 18 | 19 | def extract_tables_from_dwg(acad, writer, skip_model=True): 20 | for layout in acad.iter_layouts(skip_model=skip_model): 21 | for row in iter_cable_tables(acad, layout.Block): 22 | writer.writerow(row) 23 | 24 | 25 | def main(): 26 | parser = optparse.OptionParser() 27 | parser.add_option('-f', '--format', 28 | choices=available_write_formats(), dest='format', 29 | metavar='FMT', default='xls', 30 | help=u"Формат файла (%s) по умолчанию - %%default" % 31 | ', '.join(available_write_formats())) 32 | parser.add_option('-m', '--model', 33 | dest='include_model', default=False, action='store_true', 34 | help=u"Включить пространство Модели в область поиска") 35 | 36 | options, args = parser.parse_args() 37 | acad = Autocad() 38 | filename = args[0] if args else u"exported_%s.%s" % (acad.doc.Name, 39 | options.format) 40 | output_table = Table() 41 | extract_tables_from_dwg(acad, output_table, not options.include_model) 42 | output_table.save(filename, options.format) 43 | 44 | if __name__ == '__main__': 45 | with utils.timing(): 46 | main() 47 | -------------------------------------------------------------------------------- /examples/cables_xls_to_autocad.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | #date: 13.02.12 4 | import sys 5 | from collections import defaultdict 6 | 7 | import xlrd 8 | 9 | from pyautocad import Autocad, ACAD, APoint 10 | from pyautocad.utils import timing 11 | from pyautocad.contrib.tables import Table 12 | 13 | 14 | HEADER_TEXT_HEIGHT = 3.5 15 | TEXT_HEIGHT = 3.0 16 | ROW_HEIGHT = 8.0 17 | TABLE_WIDTH = 287 18 | TABLE_GAP = 100 19 | FIRST_TABLE_ROWS = 23 20 | NEXT_TABLE_ROWS = 27 21 | 22 | 23 | def add_cables_list_to_autocad(block, data): 24 | insert_point = APoint(20, 0) 25 | distance = APoint(TABLE_WIDTH + TABLE_GAP, 0, 0) 26 | 27 | add_cables_table(block, data[:FIRST_TABLE_ROWS], APoint(20, 0)) 28 | for chunk in chunks(data[FIRST_TABLE_ROWS:], NEXT_TABLE_ROWS): 29 | insert_point += distance 30 | add_cables_table(block, chunk, insert_point) 31 | 32 | # TODO names of pivot tables 33 | margin = APoint(0, TEXT_HEIGHT, 0) 34 | insert_point += distance 35 | block.AddText(u'Сводная таблица длин кабелей', insert_point + margin, TEXT_HEIGHT) 36 | add_pivot_table(block, insert_point, list(calc_pivot_table(data))) 37 | 38 | insert_point += distance 39 | block.AddText(u'Сводная таблица кабельных разделок', insert_point + margin, TEXT_HEIGHT) 40 | pivot_dcount = list(calc_pivot_table(data, count_double_pivot)) 41 | add_pivot_table(block, insert_point, pivot_dcount) 42 | 43 | insert_point += distance 44 | block.AddText(u'ВНИМАНИЕ! У кабелей со сложным сечением (например 4х(5х70)' 45 | u' и т.п.) указано количество разделок', 46 | insert_point + margin * 4, TEXT_HEIGHT) 47 | block.AddText(u'Сводная таблица наконечников', insert_point + margin, TEXT_HEIGHT) 48 | add_pivot_table(block, insert_point, list(calc_pivot_tips(pivot_dcount))) 49 | 50 | 51 | def read_cables_from_table(filename): 52 | data = Table.data_from_file(filename) 53 | for row in data: 54 | columns = [] 55 | for col in row: 56 | try: 57 | col = unicode(int(float(col))) # TODO HACK manipulate table format 58 | except ValueError: 59 | pass 60 | columns.append(col) 61 | yield columns 62 | 63 | 64 | def add_cables_table(block, entries, pos): 65 | table = prepare_cables_table(block, pos, len(entries)) 66 | table.RegenerateTableSuppressed = True # speedup edit operations 67 | try: 68 | for row, row_data in enumerate(entries, 3): 69 | for col, text in enumerate(row_data): 70 | table.SetCellTextHeight(row, col, TEXT_HEIGHT) 71 | if text: 72 | table.SetText(row, col, text) 73 | finally: 74 | table.RegenerateTableSuppressed = False 75 | 76 | 77 | def prepare_cables_table(block, pos, rows): 78 | table = block.AddTable(pos, rows + 5, 9, ROW_HEIGHT, 15.0) 79 | table.RegenerateTableSuppressed = True 80 | table.DeleteRows(0, 2) 81 | table.SetAlignment(ACAD.acDataRow, ACAD.acMiddleCenter) 82 | table.VertCellMargin = 0.5 83 | table.HorzCellMargin = 0.5 84 | 85 | content = ((u"Обозначение кабеля, провода", u"Трасса", "", u"Кабель, провод", "", "", "", "",), 86 | ("", u"Начало", u"Конец", u"По проекту", "", "", u"Проложен", "", ), 87 | ("", "", "", u"Марка", u"Кол., число и сечение жил", u"Длина, м", u"Марка", u"Кол., число и сечение жил", u"Длина, м")) 88 | 89 | merged = ((1, 1, 6, 8), (0, 2, 0, 0), (1, 2, 1, 1), (0, 0, 3, 8), 90 | (0, 0, 0, 0), (1, 2, 2, 2), (1, 1, 3, 5), (0, 0, 1, 2)) 91 | 92 | col_widths = [25, 60, 60, 20, 35, 16, 20, 35, 16] 93 | 94 | for col, width in enumerate(col_widths): 95 | table.SetColumnWidth(col, width) 96 | table.SetRowHeight(2, 20) 97 | 98 | # Merge cells before inserting text 99 | for merge in merged: 100 | table.MergeCells(*merge) 101 | for row, lst in enumerate(content): 102 | for col, text in enumerate(lst): 103 | table.SetCellTextHeight(row, col, HEADER_TEXT_HEIGHT) 104 | if text: 105 | table.SetText(row, col, text) 106 | table.RegenerateTableSuppressed = False 107 | return table 108 | 109 | 110 | def chunks(thing, chunk_length): 111 | for i in xrange(0, len(thing), chunk_length): 112 | yield thing[i:i + chunk_length] 113 | 114 | 115 | def add_pivot_table(block, pos, pivot): 116 | table = block.AddTable(pos, len(pivot) + 5, len(pivot[0]), ROW_HEIGHT, 20.0) 117 | table.RegenerateTableSuppressed = True 118 | table.SetColumnWidth(0, 35) 119 | table.DeleteRows(0, 2) # delete Header and Title. SuppressHeader and SuppressTitle is not working. 120 | table.SetAlignment(ACAD.acDataRow, ACAD.acMiddleCenter) 121 | table.VertCellMargin = 0.5 122 | table.HorzCellMargin = 0.5 123 | for row, columns in enumerate(pivot): 124 | for col, data in enumerate(columns): 125 | table.SetCellTextHeight(row, col, TEXT_HEIGHT) 126 | if data: 127 | table.SetText(row, col, unicode(data)) 128 | table.RegenerateTableSuppressed = False 129 | return table 130 | 131 | 132 | def length_pivot(value): 133 | return value 134 | 135 | 136 | def count_pivot(value): 137 | return 1 if value else 0 138 | 139 | 140 | def count_double_pivot(value): 141 | return count_pivot(value) * 2 142 | 143 | 144 | def try_convert(val, val_type): 145 | try: 146 | return val_type(val) 147 | except ValueError: 148 | return val_type() 149 | 150 | 151 | def normalize_section(section): 152 | section = section.replace(u'х', u'x') # replace russian h letter 153 | section = section.replace(',', '.') 154 | return section 155 | 156 | 157 | def calc_pivot_table(data, value_extractor=length_pivot): 158 | first_key = 4 159 | second_key = 3 160 | value_key = 5 161 | pivot = defaultdict(lambda: defaultdict(int)) 162 | cable_types = set() 163 | 164 | for columns in data: 165 | pivot[columns[first_key]][columns[second_key]] += value_extractor(try_convert(columns[value_key], int)) 166 | cable_types.add(columns[second_key]) 167 | cable_sections = sorted(pivot.keys()) 168 | 169 | def sections_key(section): 170 | if '(' in section: # it's hard to handle multicable feeders 171 | return section # so return untouched (will be on top) 172 | section = normalize_section(section) 173 | return map(lambda x: try_convert(x, float), section.split('x')) 174 | cable_sections = sorted(cable_sections, key=sections_key) 175 | 176 | yield [u'Cечение'] + list(sorted(cable_types)) 177 | for cable_section in cable_sections: 178 | columns = [cable_section] 179 | for cable_type in cable_types: 180 | columns.append(pivot[cable_section][cable_type]) 181 | yield columns 182 | 183 | 184 | def calc_pivot_tips(pivot_dcount): 185 | tip_counts = [] 186 | for row in pivot_dcount[1:]: 187 | tip_counts.append((row[0], sum(row[1:]))) 188 | result = defaultdict(int) 189 | for section, count in tip_counts: 190 | if '(' in section: 191 | result[section] += count 192 | continue 193 | section = normalize_section(section) 194 | count_sect = map(lambda x: try_convert(x, float), section.split('x', 2)) # TODO buggy 195 | if len(count_sect) == 2: 196 | result[count_sect[1]] += int(count_sect[0] * count) 197 | 198 | yield u'Сечение', u'Кол-во наконечников' 199 | for sect in sorted(result.keys()): 200 | yield sect, result[sect] 201 | 202 | 203 | def main(): 204 | filename = sys.argv[1] if sys.argv[1:] else 'cables_list.xls' 205 | acad = Autocad() 206 | data = list(read_cables_from_table(filename)) 207 | add_cables_list_to_autocad(acad.doc.ActiveLayout.Block, data) 208 | 209 | 210 | if __name__ == "__main__": 211 | with timing(): 212 | main() 213 | -------------------------------------------------------------------------------- /examples/calc_cb_place_consumption.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import re 4 | 5 | from pyautocad import Autocad 6 | from pyautocad.utils import mtext_to_string, timing 7 | 8 | 9 | def main(): 10 | acad = Autocad() 11 | print u'Примерный подсчет занимаемого места' 12 | print '%-20s| %s' % (u'Имя щита', u'Общее число модулей') 13 | for layout in acad.iter_layouts(): 14 | table = acad.find_one('table', layout.Block, lambda x: x.Columns == 5) 15 | if not table: 16 | continue 17 | 18 | total_modules = 0 19 | row = -1 20 | while row < table.Rows: 21 | row += 1 22 | item_str = mtext_to_string(table.GetText(row, 2)) 23 | item_str = item_str.replace(u'четырехполюсный', u'4-х')\ 24 | .replace(u'трехполюсный', u'3-х')\ 25 | .replace(u'двухполюсный', u'2-х')\ 26 | .replace(u'однополюсный', u'1-но') 27 | m = re.match(ur'.*(\d)-([xх]|но).*', item_str) 28 | if m: 29 | n_modules = int(m.group(1)) 30 | quantity = int(mtext_to_string(table.GetText(row, 3))) 31 | row += 1 # skip next row 32 | else: 33 | m = re.match(ur'(\d)[PР].*', item_str) 34 | if not m: 35 | continue 36 | n_modules = int(m.group(1)) 37 | quantity = int(mtext_to_string(table.GetText(row - 1, 3))) 38 | total_modules += n_modules * quantity 39 | print '%-20s| %s' % (layout.Name, total_modules) 40 | 41 | 42 | if __name__ == "__main__": 43 | with timing(): 44 | main() 45 | raw_input(u'\nPress enter to exit...') 46 | -------------------------------------------------------------------------------- /examples/dev_get_table_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | #date: 13.02.12 4 | import pprint 5 | 6 | from pyautocad import Autocad, utils 7 | 8 | 9 | def print_table_info(table, print_rows=0): 10 | merged = set() 11 | column_widths = [round(table.GetColumnWidth(col), 2) for col in xrange(table.Columns)] 12 | row_heights = [round(table.GetRowHeight(row), 2) for row in xrange(table.Rows)] 13 | row_texts = [] 14 | for row in range(table.Rows): 15 | columns = [] 16 | for col in range(table.Columns): 17 | if print_rows > 0: 18 | columns.append(table.GetText(row, col)) 19 | minRow, maxRow, minCol, maxCol, is_merged = table.IsMergedCell(row, col) 20 | if is_merged: 21 | merged.add((minRow, maxRow, minCol, maxCol,)) 22 | if print_rows > 0: 23 | print_rows -= 1 24 | row_texts.append(columns) 25 | 26 | print 'row_heights = %s' % str(row_heights) 27 | print 'column_widths = %s' % str(column_widths) 28 | print 'merged_cells = %s' % pprint.pformat(list(merged)) 29 | if row_texts: 30 | print 'content = [' 31 | for row in row_texts: 32 | print u" [%s]," % u", ".join("u'%s'" % s for s in row) 33 | print ']' 34 | 35 | 36 | def main(): 37 | acad = Autocad() 38 | layout = acad.doc.ActiveLayout 39 | table = acad.find_one('table', layout.Block) 40 | if not table: 41 | return 42 | print_table_info(table, 3) 43 | 44 | 45 | if __name__ == '__main__': 46 | with utils.timing(): 47 | main() 48 | -------------------------------------------------------------------------------- /examples/get_names.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import codecs 4 | import sys 5 | 6 | from pyautocad import Autocad, APoint, utils 7 | 8 | 9 | def iter_drawings_names(acad, doc): 10 | num_layouts = doc.Layouts.Count - 2 11 | for layout_number, layout in enumerate(acad.iter_layouts(doc)): 12 | utils.dynamic_print(' Layout %02d/%02d' % (layout_number, num_layouts)) 13 | # first we need to find our main stamp with name 'f4' 14 | block = acad.find_one('blockreference', layout.Block, lambda x: 'f4' in x.EffectiveName) 15 | if not block: 16 | continue 17 | block_pos = APoint(block.InsertionPoint) 18 | # approximate position of drawing name 19 | name_pos = block_pos + APoint(-90, 12) 20 | for mt in acad.iter_objects("mtext", layout.Block): 21 | if name_pos.distance_to(mt.InsertionPoint) < 5.0: 22 | text = mt.TextString 23 | yield text.replace(" \\P", " ").replace("\\P", " ") 24 | break 25 | print 26 | 27 | def main(): 28 | filename = sys.argv[1] if sys.argv[1:] else 'names.txt' 29 | output = codecs.open(filename, "w", encoding='utf-8') 30 | acad = Autocad() 31 | for doc in acad.app.Documents: 32 | print doc.Name 33 | output.write(u'%s\n' % ('-' * 50)) 34 | output.write(u" %s\n" % doc.Name) 35 | output.write(u'%s\n' % ('-' * 50)) 36 | for drawing_name in iter_drawings_names(acad, doc): 37 | output.write(u'%s\n' % drawing_name) 38 | 39 | 40 | if __name__ == "__main__": 41 | with utils.timing(): 42 | main() 43 | -------------------------------------------------------------------------------- /examples/lights.py: -------------------------------------------------------------------------------- 1 | #/usr/bin/env python 2 | #-*- coding: utf-8 -*- 3 | import re 4 | import sys 5 | from collections import namedtuple, defaultdict 6 | 7 | from pyautocad import Autocad 8 | from pyautocad import utils 9 | 10 | 11 | LampEntry = namedtuple('LampEntry', 'number, mark, numxpower') 12 | 13 | # \A1;2ARCTIC SMC/SAN 254 \S2х54/2,5;\P300 лк 14 | def iter_lamps(acad, objects): 15 | for obj in acad.iter_objects(('MText', 'MLeader'), block=objects): 16 | try: 17 | text = obj.TextString 18 | except Exception: 19 | continue 20 | text = utils.unformat_mtext(text) 21 | m = re.search(ur'(?P\d+)(?P.*?)\\S(?P.*?)/.*?;', text) 22 | if not m: 23 | continue 24 | print m.group('num'), m.group('mark'), m.group('num_power') 25 | yield LampEntry(m.group('num'), m.group('mark'), m.group('num_power')) 26 | 27 | def main(): 28 | acad = Autocad() 29 | objects = None 30 | if 'i' in sys.argv[1:]: 31 | objects = acad.get_selection('Select objects') 32 | lamps = defaultdict(int) 33 | for lamp in iter_lamps(acad, objects): 34 | lamps[lamp.mark] += int(lamp.number) 35 | print '-' * 79 36 | for mark, number in sorted(lamps.iteritems()): 37 | print '%-20s | %s' % (mark, number) 38 | 39 | if __name__ == "__main__": 40 | with utils.timing(): 41 | main() 42 | -------------------------------------------------------------------------------- /hello_world.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from pyautocad import Autocad, APoint 4 | 5 | 6 | acad = Autocad() 7 | acad.prompt("Hello, Autocad from Python\n") 8 | print acad.doc.Name 9 | 10 | p1 = APoint(0, 0) 11 | p2 = APoint(50, 25) 12 | for i in range(5): 13 | text = acad.model.AddText('Hi %s!' % i, p1, 2.5) 14 | acad.model.AddLine(p1, p2) 15 | acad.model.AddCircle(p1, 10) 16 | p1.y += 10 17 | 18 | dp = APoint(10, 0) 19 | for text in acad.iter_objects('Text'): 20 | print('text: %s at: %s' % (text.TextString, text.InsertionPoint)) 21 | text.InsertionPoint = APoint(text.InsertionPoint) + dp 22 | 23 | for obj in acad.iter_objects(['Circle', 'Line']): 24 | print(obj.ObjectName) -------------------------------------------------------------------------------- /pyautocad/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | pyautocad 5 | ~~~~~~~~~ 6 | 7 | AutoCAD Automation for Python. 8 | 9 | :copyright: (c) 2012 by Roman Haritonov. 10 | :license: BSD, see LICENSE.txt for more details. 11 | """ 12 | __docformat__ = 'restructuredtext en' 13 | __version__ = '0.2.0' 14 | 15 | from pyautocad.api import * 16 | from pyautocad.types import * 17 | -------------------------------------------------------------------------------- /pyautocad/api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | pyautocad.api 5 | ~~~~~~~~~~~~~~~ 6 | 7 | Main AutoCAD automation object. 8 | 9 | :copyright: (c) 2012 by Roman Haritonov. 10 | :license: BSD, see LICENSE.txt for more details. 11 | """ 12 | 13 | __all__ = ['Autocad', 'ACAD'] 14 | 15 | import logging 16 | import comtypes 17 | import glob 18 | import os 19 | try: 20 | import comtypes.client 21 | # generate modules for work with ACAD constants 22 | for pattern in ("acax*enu.tlb", "axdb*enu.tlb"): 23 | pattern = os.path.join( 24 | r"C:\Program Files\Common Files\Autodesk Shared", 25 | pattern 26 | ) 27 | tlib = glob.glob(pattern)[0] 28 | comtypes.client.GetModule(tlib) 29 | import comtypes.gen.AutoCAD as ACAD 30 | except Exception: 31 | # we are under readthedocs.org and need to mock this 32 | ACAD = None 33 | 34 | import pyautocad.types 35 | from pyautocad.compat import basestring, xrange 36 | 37 | logger = logging.getLogger(__name__) 38 | 39 | 40 | class Autocad(object): 41 | """Main AutoCAD Automation object 42 | """ 43 | 44 | def __init__(self, create_if_not_exists=False, visible=True): 45 | """ 46 | :param create_if_not_exists: if AutoCAD doesn't run, then 47 | new instanse will be crated 48 | :param visible: new AutoCAD instance will be visible if True (default) 49 | """ 50 | self._create_if_not_exists = create_if_not_exists 51 | self._visible = visible 52 | self._app = None 53 | 54 | @property 55 | def app(self): 56 | """Returns active :class:`AutoCAD.Application` 57 | 58 | if :class:`Autocad` was created with :data:`create_if_not_exists=True`, 59 | it will create :class:`AutoCAD.Application` if there is no active one 60 | """ 61 | if self._app is None: 62 | try: 63 | self._app = comtypes.client.GetActiveObject('AutoCAD.Application', dynamic=True) 64 | except WindowsError: 65 | if not self._create_if_not_exists: 66 | raise 67 | self._app = comtypes.client.CreateObject('AutoCAD.Application', dynamic=True) 68 | self._app.Visible = self._visible 69 | return self._app 70 | 71 | @property 72 | def doc(self): 73 | """ Returns `ActiveDocument` of current :attr:`Application`""" 74 | return self.app.ActiveDocument 75 | 76 | #: Same as :attr:`doc` 77 | ActiveDocument = doc 78 | 79 | #: Same as :attr:`app` 80 | Application = app 81 | 82 | @property 83 | def model(self): 84 | """ `ModelSpace` from active document """ 85 | return self.doc.ModelSpace 86 | 87 | def iter_layouts(self, doc=None, skip_model=True): 88 | """Iterate layouts from *doc* 89 | 90 | :param doc: document to iterate layouts from if `doc=None` (default), :attr:`ActiveDocument` is used 91 | :param skip_model: don't include :class:`ModelSpace` if `True` 92 | """ 93 | if doc is None: 94 | doc = self.doc 95 | for layout in sorted(doc.Layouts, key=lambda x: x.TabOrder): 96 | if skip_model and not layout.TabOrder: 97 | continue 98 | yield layout 99 | 100 | def iter_objects(self, object_name_or_list=None, block=None, 101 | limit=None, dont_cast=False): 102 | """Iterate objects from `block` 103 | 104 | :param object_name_or_list: part of object type name, or list of it 105 | :param block: Autocad block, default - :class:`ActiveDocument.ActiveLayout.Block` 106 | :param limit: max number of objects to return, default infinite 107 | :param dont_cast: don't retrieve best interface for object, may speedup 108 | iteration. Returned objects should be casted by caller 109 | """ 110 | if block is None: 111 | block = self.doc.ActiveLayout.Block 112 | object_names = object_name_or_list 113 | if object_names: 114 | if isinstance(object_names, basestring): 115 | object_names = [object_names] 116 | object_names = [n.lower() for n in object_names] 117 | 118 | count = block.Count 119 | for i in xrange(count): 120 | item = block.Item(i) # it's faster than `for item in block` 121 | if limit and i >= limit: 122 | return 123 | if object_names: 124 | object_name = item.ObjectName.lower() 125 | if not any(possible_name in object_name for possible_name in object_names): 126 | continue 127 | if not dont_cast: 128 | item = self.best_interface(item) 129 | yield item 130 | 131 | def iter_objects_fast(self, object_name_or_list=None, container=None, limit=None): 132 | """Shortcut for `iter_objects(dont_cast=True)` 133 | 134 | Shouldn't be used in normal situations 135 | """ 136 | return self.iter_objects(object_name_or_list, container, limit, dont_cast=True) 137 | 138 | def find_one(self, object_name_or_list, container=None, predicate=None): 139 | """Returns first occurance of object which match `predicate` 140 | 141 | :param object_name_or_list: like in :meth:`iter_objects` 142 | :param container: like in :meth:`iter_objects` 143 | :param predicate: callable, which accepts object as argument 144 | and returns `True` or `False` 145 | :returns: Object if found, else `None` 146 | """ 147 | if predicate is None: 148 | predicate = bool 149 | for obj in self.iter_objects(object_name_or_list, container): 150 | if predicate(obj): 151 | return obj 152 | return None 153 | 154 | def best_interface(self, obj): 155 | """ Retrieve best interface for object """ 156 | return comtypes.client.GetBestInterface(obj) 157 | 158 | def prompt(self, text): 159 | """ Prints text in console and in `AutoCAD` prompt 160 | """ 161 | print(text) 162 | self.doc.Utility.Prompt(u"%s\n" % text) 163 | 164 | def get_selection(self, text="Select objects"): 165 | """ Asks user to select objects 166 | 167 | :param text: prompt for selection 168 | """ 169 | self.prompt(text) 170 | try: 171 | self.doc.SelectionSets.Item("SS1").Delete() 172 | except Exception: 173 | logger.debug('Delete selection failed') 174 | 175 | selection = self.doc.SelectionSets.Add('SS1') 176 | selection.SelectOnScreen() 177 | return selection 178 | 179 | #: shortcut for :func:`pyautocad.types.aDouble` 180 | aDouble = staticmethod(pyautocad.types.aDouble) 181 | #: shortcut for :func:`pyautocad.types.aInt` 182 | aInt = staticmethod(pyautocad.types.aInt) 183 | #: shortcut for :func:`pyautocad.types.aShort` 184 | aShort = staticmethod(pyautocad.types.aShort) 185 | 186 | -------------------------------------------------------------------------------- /pyautocad/cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | .. versionadded:: 0.1.2 5 | 6 | pyautocad.cache 7 | ~~~~~~~~~~~~~~~ 8 | 9 | Proxy to cache all object attributes. 10 | 11 | :copyright: (c) 2012 by Roman Haritonov. 12 | :license: BSD, see LICENSE.txt for more details. 13 | """ 14 | from __future__ import print_function 15 | 16 | 17 | class Cached(object): 18 | """ 19 | Proxy for caching object attributes. 20 | 21 | Consider external class `Foo` with expensive property (we can't change its code):: 22 | 23 | class Foo(object): 24 | @property 25 | def x(self): 26 | print 'consuming time' 27 | time.sleep(1) 28 | return 42 29 | 30 | Cache all attributes and test access:: 31 | 32 | foo = Foo() 33 | cached_foo = Cached(foo) 34 | for i in range(10): 35 | print cached_foo.x 36 | 37 | Output:: 38 | 39 | consuming time 40 | 42 41 | 42 42 | 42 43 | 42 44 | 42 45 | 46 | It's possible to switch caching off with :meth:`switch_caching` 47 | and retrieve original instance with :meth:`get_original` 48 | """ 49 | def __init__(self, instance): 50 | object.__setattr__(self, '_instance', instance) 51 | object.__setattr__(self, '_is_enabled', True) 52 | object.__setattr__(self, '_storage', {}) 53 | 54 | def get_original(self): 55 | """ Returns original instance 56 | """ 57 | return self._instance 58 | 59 | def switch_caching(self, is_enabled): 60 | """ Switch caching on or off 61 | 62 | :param is_enabled: caching status `True` or `False` 63 | :type is_enabled: bool 64 | """ 65 | self._is_enabled = is_enabled 66 | if not is_enabled: 67 | self._storage = {} 68 | 69 | def __setattr__(self, key, value): 70 | if key in self.__dict__: 71 | return object.__setattr__(self, key, value) 72 | object.__setattr__(self._instance, key, value) 73 | if self._is_enabled: 74 | self._storage[key] = value 75 | 76 | def __getattr__(self, key): 77 | if key in self.__dict__: 78 | return object.__getattribute__(self, key) 79 | storage = self._storage 80 | if self._is_enabled and key in storage: 81 | return storage[key] 82 | value = getattr(self._instance, key) 83 | storage[key] = value 84 | return value 85 | 86 | def __delattr__(self, key): 87 | if key in self.__dict__: 88 | return object.__delattr__(self, key) 89 | if key in self._storage: 90 | del self._storage[key] 91 | object.__delattr__(self._instance, key) 92 | 93 | 94 | if __name__ == "__main__": 95 | import time 96 | 97 | class Foo(object): 98 | @property 99 | def x(self): 100 | print('consuming time') 101 | time.sleep(1) 102 | return 42 103 | 104 | foo = Foo() 105 | cached_foo = Cached(foo) 106 | for i in range(5): 107 | print(cached_foo.x) 108 | -------------------------------------------------------------------------------- /pyautocad/compat.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | IS_PY3 = sys.version_info[0] == 3 4 | 5 | if IS_PY3: 6 | unicode = str 7 | bytes = bytes 8 | basestring = str 9 | xrange = range 10 | else: 11 | unicode = unicode 12 | _orig_bytes = bytes 13 | bytes = lambda s, *a: _orig_bytes(s) 14 | basestring = basestring 15 | xrange = xrange -------------------------------------------------------------------------------- /pyautocad/contrib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reclosedev/pyautocad/527a672099b1b3b95e055fcbbbb3b67e305f0fe5/pyautocad/contrib/__init__.py -------------------------------------------------------------------------------- /pyautocad/contrib/tables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | pyautocad.contrib.tables 5 | ~~~~~~~~~~~~~~~~~~~~~~~~ 6 | 7 | Import and export tabular data from popular formats. 8 | 9 | :copyright: (c) 2012 by Roman Haritonov. 10 | :license: BSD, see LICENSE.txt for more details. 11 | """ 12 | 13 | import csv 14 | import json 15 | import os 16 | 17 | import tablib 18 | import xlrd 19 | 20 | class FormatNotSupported(Exception): 21 | pass 22 | 23 | class Table(object): 24 | """ Represents table with ability to import and export data to following formats: 25 | 26 | - csv 27 | - xls 28 | - xlsx (write only) 29 | - json 30 | 31 | When you need to store some data, it can be done as follows:: 32 | 33 | table = Table() 34 | for i in range(5): 35 | table.writerow([i, i, i]) 36 | 37 | table.save('data.xls', 'xls') 38 | 39 | To import data from file, use :meth:`data_from_file`:: 40 | 41 | data = Table.data_from_file('data.xls') 42 | 43 | """ 44 | 45 | _write_formats = set(['csv', 'xls', 'xlsx', 'json']) 46 | _read_formats = _write_formats - set(['xlsx']) 47 | 48 | def __init__(self): 49 | self.dataset = tablib.Dataset() 50 | 51 | def writerow(self, row): 52 | """ Add `row` to table 53 | 54 | :param row: row to add 55 | :type row: list or tuple 56 | """ 57 | self.dataset.append(row) 58 | 59 | def append(self, row): 60 | """ Synonym for :meth:`writerow` 61 | """ 62 | self.writerow(row) 63 | 64 | def clear(self): 65 | """ Clear current table 66 | """ 67 | self.dataset = tablib.Dataset() 68 | 69 | def save(self, filename, fmt, encoding='cp1251'): 70 | """ Save data to file 71 | 72 | :param filename: path to file 73 | :param fmt: data format (one of supported, e.g. 'xls', 'csv' 74 | :param encoding: encoding for 'csv' format 75 | """ 76 | self._raise_if_bad_format(fmt) 77 | with open(filename, 'wb') as output: 78 | if encoding is not None and fmt == 'csv': 79 | self.to_csv(output, encoding) 80 | else: 81 | output.write(self.convert(fmt)) 82 | 83 | def convert(self, fmt): 84 | """ Return data, converted to format 85 | 86 | :param fmt: desirable format of data 87 | 88 | **Note**: to convert to `csv` format, use :meth:`to_csv` 89 | 90 | See also :meth:`available_write_formats` 91 | """ 92 | self._raise_if_bad_format(fmt) 93 | return getattr(self.dataset, fmt) 94 | 95 | def _raise_if_bad_format(self, fmt): 96 | if fmt not in self._write_formats: 97 | raise FormatNotSupported('Unknown format: %s' % fmt) 98 | 99 | def to_csv(self, stream, encoding='cp1251', delimiter=';', **kwargs): 100 | """ Writes data in `csv` format to stream 101 | 102 | :param stream: stream to write data to 103 | :param encoding: output encoding 104 | :param delimiter: `csv` delimiter 105 | :param kwargs: additional parameters for :class:`csv.writer` 106 | """ 107 | writer = csv.writer(stream, delimiter=delimiter, **kwargs) 108 | for row in self.dataset.dict: 109 | row = [c.encode(encoding) for c in row] 110 | writer.writerow(row) 111 | 112 | @staticmethod 113 | def data_from_file(filename, fmt=None, csv_encoding='cp1251', csv_delimiter=';'): 114 | """ Returns data in desired format from file 115 | 116 | :param filename: path to file with data 117 | :param fmt: format of file, if it's `None`, then it tries to guess format 118 | from `filename` extension 119 | :param csv_encoding: encoding for `csv` data 120 | :param csv_delimiter: delimiter for `csv` data 121 | 122 | Format should be in :meth:`available_read_formats` 123 | """ 124 | if fmt is None: 125 | fmt = os.path.splitext(filename)[1][1:] 126 | raw_data = _TableImporter(csv_encoding=csv_encoding, 127 | csv_delimiter=csv_delimiter).import_table(filename, fmt) 128 | return raw_data 129 | 130 | @staticmethod 131 | def available_write_formats(): 132 | 133 | return list(Table._write_formats) 134 | 135 | @staticmethod 136 | def available_read_formats(): 137 | return list(Table._read_formats) 138 | 139 | 140 | class _TableImporter(object): 141 | def __init__(self, csv_encoding='cp1251', csv_delimiter=';'): 142 | self.csv_encoding = csv_encoding 143 | self.csv_delimiter = csv_delimiter 144 | 145 | def import_table(self, filename, fmt): 146 | reader = getattr(self, 'read_%s' % fmt, None) 147 | if reader is None: 148 | raise FormatNotSupported('Unknown fmt: %s' % fmt) 149 | dataset = [] 150 | with open(filename, 'rb') as stream: 151 | for row in reader(stream): 152 | dataset.append(row) 153 | return dataset 154 | 155 | def read_csv(self, stream): 156 | reader = csv.reader(stream, delimiter=self.csv_delimiter) 157 | for row in reader: 158 | yield [c.decode(self.csv_encoding) for c in row] 159 | 160 | def read_xls(self, stream): 161 | book = xlrd.open_workbook(file_contents=stream.read()) 162 | sheet = book.sheet_by_index(0) 163 | for row in xrange(sheet.nrows): 164 | columns = [] 165 | for col in xrange(sheet.ncols): 166 | val = sheet.cell(row, col).value 167 | columns.append(val) 168 | yield columns 169 | 170 | def read_json(self, stream): 171 | return json.load(stream) 172 | 173 | available_write_formats = Table.available_write_formats # backward compatibility 174 | available_read_formats = Table.available_read_formats 175 | 176 | -------------------------------------------------------------------------------- /pyautocad/types.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | pyautocad.types 5 | ~~~~~~~~~~~~~~~ 6 | 7 | 3D Points and and other AutoCAD data types. 8 | 9 | :copyright: (c) 2012 by Roman Haritonov. 10 | :license: BSD, see LICENSE.txt for more details. 11 | """ 12 | import array 13 | import operator 14 | import math 15 | 16 | from pyautocad.compat import IS_PY3 17 | 18 | 19 | class APoint(array.array): 20 | """ 3D point with basic geometric operations and support for passing as a 21 | parameter for `AutoCAD` Automation functions 22 | 23 | Usage:: 24 | 25 | >>> p1 = APoint(10, 10) 26 | >>> p2 = APoint(20, 20) 27 | >>> p1 + p2 28 | APoint(30.00, 30.00, 0.00) 29 | 30 | Also it supports iterable as parameter:: 31 | 32 | >>> APoint([10, 20, 30]) 33 | APoint(10.00, 20.00, 30.00) 34 | >>> APoint(range(3)) 35 | APoint(0.00, 1.00, 2.00) 36 | 37 | Supported math operations: `+`, `-`, `*`, `/`, `+=`, `-=`, `*=`, `/=`:: 38 | 39 | >>> p = APoint(10, 10) 40 | >>> p + p 41 | APoint(20.00, 20.00, 0.00) 42 | >>> p + 10 43 | APoint(20.00, 20.00, 10.00) 44 | >>> p * 2 45 | APoint(20.00, 20.00, 0.00) 46 | >>> p -= 1 47 | >>> p 48 | APoint(9.00, 9.00, -1.00) 49 | 50 | It can be converted to `tuple` or `list`:: 51 | 52 | >>> tuple(APoint(1, 1, 1)) 53 | (1.0, 1.0, 1.0) 54 | 55 | """ 56 | def __new__(cls, x_or_seq, y=0.0, z=0.0): 57 | if isinstance(x_or_seq, (array.array, list, tuple)) and len(x_or_seq) == 3: 58 | return super(APoint, cls).__new__(cls, 'd', x_or_seq) 59 | return super(APoint, cls).__new__(cls, 'd', (x_or_seq, y, z)) 60 | 61 | 62 | @property 63 | def x(self): 64 | """ x coordinate of 3D point""" 65 | return self[0] 66 | 67 | @x.setter 68 | def x(self, value): 69 | self[0] = value 70 | 71 | @property 72 | def y(self): 73 | """ y coordinate of 3D point""" 74 | return self[1] 75 | 76 | @y.setter 77 | def y(self, value): 78 | self[1] = value 79 | 80 | @property 81 | def z(self): 82 | """ z coordinate of 3D point""" 83 | return self[2] 84 | 85 | @z.setter 86 | def z(self, value): 87 | self[2] = value 88 | 89 | def distance_to(self, other): 90 | """ Returns distance to `other` point 91 | 92 | :param other: :class:`APoint` instance or any sequence of 3 coordinates 93 | """ 94 | return distance(self, other) 95 | 96 | def __add__(self, other): 97 | return self.__left_op(self, other, operator.add) 98 | 99 | def __sub__(self, other): 100 | return self.__left_op(self, other, operator.sub) 101 | 102 | def __mul__(self, other): 103 | return self.__left_op(self, other, operator.mul) 104 | 105 | if IS_PY3: 106 | def __div__(self, other): 107 | return self.__left_op(self, other, operator.truediv) 108 | else: 109 | def __div__(self, other): 110 | return self.__left_op(self, other, operator.div) 111 | 112 | __radd__ = __add__ 113 | __rsub__ = __sub__ 114 | __rmul__ = __mul__ 115 | __rdiv__ = __div__ 116 | __floordiv__ = __div__ 117 | __rfloordiv__ = __div__ 118 | __truediv__ = __div__ 119 | _r_truediv__ = __div__ 120 | 121 | def __neg__(self): 122 | return self.__left_op(self, -1, operator.mul) 123 | 124 | def __left_op(self, p1, p2, op): 125 | if isinstance(p2, (float, int)): 126 | return APoint(op(p1[0], p2), op(p1[1], p2), op(p1[2], p2)) 127 | return APoint(op(p1[0], p2[0]), op(p1[1], p2[1]), op(p1[2], p2[2])) 128 | 129 | def __iadd__(self, p2): 130 | return self.__iop(p2, operator.add) 131 | 132 | def __isub__(self, p2): 133 | return self.__iop(p2, operator.sub) 134 | 135 | def __imul__(self, p2): 136 | return self.__iop(p2, operator.mul) 137 | 138 | def __idiv__(self, p2): 139 | return self.__iop(p2, operator.div) 140 | 141 | def __iop(self, p2, op): 142 | if isinstance(p2, (float, int)): 143 | self[0] = op(self[0], p2) 144 | self[1] = op(self[1], p2) 145 | self[2] = op(self[2], p2) 146 | else: 147 | self[0] = op(self[0], p2[0]) 148 | self[1] = op(self[1], p2[1]) 149 | self[2] = op(self[2], p2[2]) 150 | return self 151 | 152 | def __repr__(self): 153 | return self.__str__() 154 | 155 | def __str__(self): 156 | return 'APoint(%.2f, %.2f, %.2f)' % tuple(self) 157 | 158 | def __eq__(self, other): 159 | if not isinstance(other, (array.array, list, tuple)): 160 | return False 161 | return tuple(self) == tuple(other) 162 | 163 | 164 | def distance(p1, p2): 165 | """ Returns distance between two points `p1` and `p2` 166 | """ 167 | return math.sqrt((p1[0] - p2[0]) ** 2 + 168 | (p1[1] - p2[1]) ** 2 + 169 | (p1[2] - p2[2]) ** 2) 170 | 171 | 172 | # next functions can accept parameters as aDouble(1, 2, 3) 173 | # or as list or tuple aDouble([1, 2, 3]) 174 | def aDouble(*seq): 175 | """ Returns :class:`array.array` of doubles ('d' code) for passing to AutoCAD 176 | 177 | For 3D points use :class:`APoint` instead. 178 | """ 179 | return _sequence_to_comtypes('d', *seq) 180 | 181 | 182 | def aInt(*seq): 183 | """ Returns :class:`array.array` of ints ('l' code) for passing to AutoCAD 184 | """ 185 | return _sequence_to_comtypes('l', *seq) 186 | 187 | 188 | def aShort(*seq): 189 | """ Returns :class:`array.array` of shorts ('h' code) for passing to AutoCAD 190 | """ 191 | return _sequence_to_comtypes('h', *seq) 192 | 193 | 194 | def _sequence_to_comtypes(typecode='d', *sequence): 195 | if len(sequence) == 1: 196 | return array.array(typecode, sequence[0]) 197 | return array.array(typecode, sequence) 198 | -------------------------------------------------------------------------------- /pyautocad/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | """ 4 | pyautocad.utils 5 | ~~~~~~~~~~~~~~~ 6 | 7 | Utility functions for work with texts, tables, etc. 8 | 9 | :copyright: (c) 2012 by Roman Haritonov. 10 | :license: BSD, see LICENSE.txt for more details. 11 | """ 12 | from __future__ import print_function 13 | 14 | import sys 15 | import re 16 | import time 17 | from contextlib import contextmanager 18 | 19 | 20 | def unformat_mtext(s, exclude_list=('P', 'S')): 21 | """Returns string with removed format information 22 | 23 | :param s: string with multitext 24 | :param exclude_list: don't touch tags from this list. Default ('P', 'S') for 25 | newline and fractions 26 | 27 | :: 28 | 29 | >>> text = ur'{\\fGOST type A|b0|i0|c204|p34;TEST\\fGOST type A|b0|i0|c0|p34;123}' 30 | >>> unformat_mtext(text) 31 | u'TEST123' 32 | 33 | """ 34 | s = re.sub(r'\{?\\[^%s][^;]+;' % ''.join(exclude_list), '', s) 35 | s = re.sub(r'\}', '', s) 36 | return s 37 | 38 | 39 | def mtext_to_string(s): 40 | """ 41 | Returns string with removed format innformation as :func:`unformat_mtext` and 42 | `\\P` (paragraphs) replaced with newlines 43 | 44 | :: 45 | 46 | >>> text = ur'{\\fGOST type A|b0|i0|c204|p34;TEST\\fGOST type A|b0|i0|c0|p34;123}\\Ptest321' 47 | >>> mtext_to_string(text) 48 | u'TEST123\\ntest321' 49 | 50 | """ 51 | 52 | return unformat_mtext(s).replace(u'\\P', u'\n') 53 | 54 | 55 | def string_to_mtext(s): 56 | """Returns string in Autocad multitext format 57 | 58 | Replaces newllines `\\\\n` with `\\\\P`, etc. 59 | """ 60 | return s.replace('\\', '\\\\').replace(u'\n', u'\P') 61 | 62 | 63 | def text_width(text_item): 64 | """Returns width of Autocad `Text` or `MultiText` object 65 | """ 66 | bbox_min, bbox_max = text_item.GetBoundingbox() 67 | return bbox_max[0] - bbox_min[0] 68 | 69 | 70 | @contextmanager 71 | def suppressed_regeneration_of(table): 72 | """ .. versionadded:: 0.1.2 73 | 74 | Context manager. Suppresses table regeneration to dramatically speedup table operations 75 | 76 | :param table: table object 77 | 78 | :: 79 | 80 | with suppressed_regeneration_of(table): 81 | populate(table) # or change its properties 82 | 83 | """ 84 | # TODO: find the way to suppress regeneration of other objects 85 | table.RegenerateTableSuppressed = True 86 | try: 87 | yield 88 | finally: 89 | table.RegenerateTableSuppressed = False 90 | 91 | @contextmanager 92 | def timing(message=u'Elapsed'): 93 | """ Context manager for timing execution 94 | 95 | :param message: message to print 96 | 97 | Usage:: 98 | 99 | with timing('some operation'): 100 | do_some_actions() 101 | 102 | Will print:: 103 | 104 | some operation: 1.000 s # where 1.000 is actual execution time 105 | 106 | """ 107 | begin = time.time() 108 | try: 109 | yield begin 110 | finally: 111 | elapsed = (time.time() - begin) 112 | print(u'%s: %.3f s' % (message, elapsed)) 113 | 114 | 115 | def dynamic_print(text): 116 | """Prints text dynamically in one line 117 | 118 | Used for printing something like animations, or progress 119 | """ 120 | sys.stdout.write('\r%s' % text) 121 | sys.stdout.flush() 122 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | try: 4 | from setuptools import setup 5 | except ImportError: 6 | from distutils.core import setup 7 | 8 | 9 | setup( 10 | name="pyautocad", 11 | packages=["pyautocad", "pyautocad.contrib"], 12 | version="0.2.0", 13 | description="AutoCAD Automation for Python", 14 | author="Roman Haritonov", 15 | author_email="reclosedev@gmail.com", 16 | url="https://github.com/reclosedev/pyautocad", 17 | install_requires=[ 18 | 'comtypes>=1.1.1', 19 | ], 20 | keywords=["autocad", "automation", "activex", "comtypes"], 21 | license="BSD License", 22 | include_package_data=True, 23 | classifiers=[ 24 | "Programming Language :: Python", 25 | "License :: OSI Approved :: BSD License", 26 | "Development Status :: 4 - Beta", 27 | "Intended Audience :: Developers", 28 | "Intended Audience :: System Administrators", 29 | "Programming Language :: Python :: 2", 30 | "Programming Language :: Python :: 3", 31 | "Programming Language :: Python :: 2.7", 32 | "Programming Language :: Python :: 3.3", 33 | "Programming Language :: Python :: 3.4", 34 | "Programming Language :: Python :: 3.5", 35 | "Programming Language :: Python :: Implementation :: CPython", 36 | "Operating System :: Microsoft :: Windows", 37 | "Topic :: Software Development :: Libraries :: Python Modules", 38 | "Topic :: Scientific/Engineering", 39 | ], 40 | long_description=open('README.rst').read() 41 | ) 42 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reclosedev/pyautocad/527a672099b1b3b95e055fcbbbb3b67e305f0fe5/tests/__init__.py -------------------------------------------------------------------------------- /tests/cmd_with_path.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | set PYTHONPATH=%PYTHONPATH%;.. 3 | cmd -------------------------------------------------------------------------------- /tests/test_api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # date: 16.01.12 4 | from __future__ import print_function 5 | import os, sys 6 | sys.path.insert(0, os.path.abspath('..')) 7 | from _ctypes import COMError 8 | import unittest 9 | 10 | from pyautocad import Autocad, aDouble, APoint, ACAD 11 | from pyautocad.compat import unicode 12 | 13 | 14 | NPASS = 3000 15 | 16 | 17 | class ApiTestCase(unittest.TestCase): 18 | def setUp(self): 19 | self.acad = Autocad(True) 20 | self.doc = self.acad.app.Documents.Add() 21 | print('Current', self.doc.Name) 22 | 23 | def tearDown(self): 24 | self.doc.Close(False) 25 | pass 26 | 27 | def test_points_arguments(self): 28 | model = self.acad.model 29 | p1 = APoint(0, 0, 0) 30 | for i in range(10): 31 | model.AddCircle(p1 * i, i + 1) 32 | p1 += i 33 | 34 | for circle in self.acad.iter_objects('circle'): 35 | cp = APoint(circle.Center) 36 | model.AddCircle(-cp, circle.Radius) 37 | 38 | def test_types(self): 39 | model = self.acad.model 40 | p1 = APoint(0, 0, 0) 41 | p2 = APoint(10, 10, 0) 42 | p3 = tuple(p + 10 for p in p2) 43 | 44 | model.AddLine(p1, p2) 45 | model.AddLine(p2, APoint(p3)) 46 | lines = list(self.acad.iter_objects()) 47 | self.assertEqual(len(lines), 2) 48 | self.assertEqual(lines[0].StartPoint, p1) 49 | self.assertEqual(lines[0].EndPoint, p2) 50 | self.assertEqual(lines[1].StartPoint, p2) 51 | self.assertEqual(lines[1].EndPoint, p3) 52 | with self.assertRaises(COMError): 53 | model.AddLine(aDouble(0, 0), APoint(0, 0, 0)) 54 | 55 | def test_text(self): 56 | model = self.acad.model 57 | text1 = u'Русский текст' 58 | text2 = u'With paragraph \PYes' 59 | 60 | t1 = model.AddText(text1, APoint(0, 0, 0), 10) 61 | t2 = model.AddText(text2, APoint(10, 10, 0), 10) 62 | 63 | self.assertEqual(type(t1.TextString), unicode) 64 | self.assertEqual(t1.TextString, text1) 65 | self.assertEqual(t2.InsertionPoint, (10, 10, 0)) 66 | self.assertNotEqual(t2.InsertionPoint, (10, 10, 1)) 67 | 68 | def test_multitext(self): 69 | model = self.acad.model 70 | text1 = 'Line1\nLine2\nLine3\\' 71 | text2 = 'Line1\\PLine2\\PLine3\\P' 72 | 73 | t1 = model.AddMText(APoint(0, 0, 0), 10, text1) 74 | t2 = model.AddMText(APoint(10, 10, 0), 10, text2) 75 | self.assertEqual(t1.TextString, text1) 76 | self.assertEqual(t2.TextString, text2) 77 | 78 | def test_iter_objects(self): 79 | model = self.acad.model 80 | p1 = APoint(0, 0, 0) 81 | p2 = APoint(10, 10, 0) 82 | n_lines = 10 83 | n_texts = 15 84 | for i in range(n_lines): 85 | model.AddLine(p1, p2) 86 | for i in range(n_texts): 87 | model.AddMText(p2, 10, u'Dummy') 88 | 89 | lines_count = len(list(self.acad.iter_objects('Line'))) 90 | texts_count = len(list(self.acad.iter_objects('MText'))) 91 | all_count = len(list(self.acad.iter_objects(['MText', 'Line']))) 92 | 93 | self.assertEqual(lines_count, n_lines) 94 | self.assertEqual(texts_count, n_texts) 95 | self.assertEqual(all_count, n_lines + n_texts) 96 | 97 | def test_find_objects(self): 98 | p1 = APoint(0, 0) 99 | model = self.acad.model 100 | for i in range(5): 101 | model.AddText(u'test %s' % i, p1, 2.5) 102 | 103 | def text_contains_3(text_obj): 104 | return '3' in text_obj.TextString 105 | 106 | text = self.acad.find_one('Text', predicate=text_contains_3) 107 | self.assertEqual(text.TextString, 'test 3') 108 | 109 | def test_some_constants_available(self): 110 | self.assertTrue(ACAD.acAlignmentRight) 111 | 112 | 113 | if __name__ == '__main__': 114 | unittest.main() 115 | -------------------------------------------------------------------------------- /tests/test_cached.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import print_function 4 | import os, sys 5 | sys.path.insert(0, os.path.abspath('..')) 6 | import unittest 7 | import mock 8 | 9 | from pyautocad.cache import Cached 10 | 11 | 12 | class C(object): 13 | def __init__(self): 14 | self._x = None 15 | 16 | def getx(self): 17 | print("get x") 18 | return self._x 19 | 20 | def setx(self, value): 21 | print("set x", value) 22 | self._x = value 23 | 24 | x = property(lambda self: self.getx(), lambda self, x: self.setx(x)) 25 | 26 | def modify(self, val): 27 | print("modify", val) 28 | self._x = val 29 | 30 | 31 | class CachedTestCase(unittest.TestCase): 32 | def test_caching_attributes(self): 33 | a = C() 34 | 35 | a.x = 1 36 | ac = Cached(a) 37 | self.assertEqual(ac.x, 1) 38 | a.modify(2) 39 | self.assertEqual(ac.x, 1) 40 | a.x = 2 41 | 42 | self.assertEqual(ac.x, 1) 43 | ac.modify(2) 44 | self.assertEqual(ac.x, 1) 45 | 46 | ac.x = 3 47 | self.assertEqual(ac.x, 3) 48 | self.assertEqual(a.x, 3) 49 | with self.assertRaises(AttributeError): 50 | y = ac.y 51 | ac.y = 10 52 | self.assertEqual(a.y, ac.y) 53 | self.assert_('y' in ac._storage) 54 | del ac.y 55 | with self.assertRaises(AttributeError): 56 | y = ac.y 57 | with self.assertRaises(AttributeError): 58 | del ac.z 59 | 60 | self.assertIs(ac.get_original(), a) 61 | 62 | a = C() 63 | ac = Cached(a) 64 | a.getx = mock.Mock(name='getx', return_value=1) 65 | for i in range(5): 66 | _ = ac.x 67 | self.assertEqual(a.getx.call_count, 1) 68 | ac.switch_caching(False) 69 | for i in range(5): 70 | _ = ac.x 71 | self.assertEqual(a.getx.call_count, 5 + 1) 72 | 73 | def test_caching_off(self): 74 | a = C() 75 | a.x = 1 76 | ac = Cached(a) 77 | a.x = 3 78 | self.assertEqual(ac.x, 3) 79 | a.x = 5 80 | self.assertEqual(ac.x, 3) 81 | ac.x = 5 82 | self.assertEqual(ac.x, 5) 83 | self.assertEqual(a.x, 5) 84 | ac.switch_caching(False) 85 | a.x = 7 86 | self.assertEqual(ac.x, 7) 87 | a.x = 8 88 | self.assertEqual(ac.x, 8) 89 | ac.switch_caching(True) 90 | a.x = 9 91 | self.assertEqual(ac.x, 8) 92 | 93 | 94 | if __name__ == '__main__': 95 | unittest.main() 96 | -------------------------------------------------------------------------------- /tests/test_contrib_tables.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import os, sys 4 | sys.path.insert(0, os.path.abspath('..')) 5 | import unittest 6 | 7 | from pyautocad.contrib import tables 8 | import tablib 9 | 10 | 11 | class TableTestCase(unittest.TestCase): 12 | 13 | def test_tablib(self): 14 | d = tablib.Dataset([1,2,3], [4,5,6]) 15 | with self.assertRaises(tablib.InvalidDimensions): 16 | d.append([1,2,3,4,5]) 17 | 18 | def test_table_create(self): 19 | t = tables.Table() 20 | t.writerow([1]*3) 21 | t.writerow([2]*3) 22 | t.append([3]*3) 23 | with self.assertRaises(tablib.InvalidDimensions): 24 | t.writerow([4]*4) 25 | self.assertEqual(t.dataset.dict, [[1]*3, [2]*3, [3]*3]) 26 | t.clear() 27 | t.writerow([1]*3) 28 | self.assertEqual(t.dataset.dict, [[1]*3]) 29 | 30 | def test_table_unknown_format(self): 31 | t = tables.Table() 32 | t.writerow([1]*3) 33 | with self.assertRaises(tables.FormatNotSupported): 34 | t.save('tst', 'any_nonexistent') 35 | with self.assertRaises(tables.FormatNotSupported): 36 | t = tables.Table.data_from_file('tst', 'any_nonexistent') 37 | 38 | def test_write_read_encoding(self): 39 | t = tables.Table() 40 | row = [u'Привет, мир', u'мирtabbed', 'some'] 41 | data = [row] 42 | t.writerow(row) 43 | #for fmt in tables.available_write_formats(): 44 | for fmt in tables.available_read_formats(): 45 | filename = 'test_hello.%s' % fmt 46 | t.save(filename, fmt) 47 | t2 = tables.Table.data_from_file(filename, fmt) 48 | self.assertEqual(t2, data) 49 | t2 = tables.Table.data_from_file(filename) 50 | self.assertEqual(t2, data) 51 | os.remove(filename) 52 | 53 | if __name__ == '__main__': 54 | unittest.main() 55 | -------------------------------------------------------------------------------- /tests/test_some_ideas.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | #date: 13.02.12 4 | import os, sys 5 | sys.path.insert(0, os.path.abspath('..')) 6 | import unittest 7 | 8 | from pyautocad import Autocad 9 | import tablib 10 | 11 | 12 | def sort_by_correct_order(messed_order, correct_order): 13 | return [x for x in correct_order if x in messed_order] + \ 14 | [x for x in messed_order if x not in correct_order] 15 | 16 | class MyTestCase(unittest.TestCase): 17 | def test_sort_by_correct(self): 18 | correct = ['TP', 'VRU', 'SHR', 'SHO', 'LAMP'] 19 | new_seq = ['SHR', 'VRU', 'LAMP', 'SHO', 'STANOK'] 20 | print sort_by_correct_order(new_seq, correct) 21 | 22 | def test_tablib(self): 23 | d = tablib.Dataset([1,2,3], [4,5,6]) 24 | with self.assertRaises(tablib.InvalidDimensions): 25 | d.append([1,2,3,4,5]) 26 | 27 | 28 | if __name__ == '__main__': 29 | unittest.main() 30 | -------------------------------------------------------------------------------- /tests/test_types.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | #date: 17.01.12 4 | import os, sys 5 | sys.path.insert(0, os.path.abspath('..')) 6 | import unittest 7 | 8 | from pyautocad.types import APoint, distance 9 | 10 | 11 | class PointTestCase(unittest.TestCase): 12 | def test_point_ops(self): 13 | p1 = APoint(1, 1, 1) 14 | p2 = APoint(1, 1, 1) 15 | p3 = APoint(2, 2, 2) 16 | p4 = APoint(2, 2, 2) 17 | 18 | self.assertEqual(p1 + p2, (2, 2, 2)) 19 | self.assertEqual(p1 - p3, (-1, -1, -1)) 20 | self.assertEqual(p1 * p2, p1) 21 | self.assertEqual(p3 * p4, (4, 4, 4)) 22 | self.assertEqual(p3 / p4, p1) 23 | 24 | self.assertEqual(p1 + 1, (2, 2, 2)) 25 | self.assertEqual(p2 * 4, p3 * 2) 26 | self.assertEqual(p3 * 10, (20, 20, 20)) 27 | self.assertEqual(p3 / 2, p1) 28 | self.assertEqual(p1 / 0.5, p3) 29 | 30 | self.assertEqual(-p1, (-1, -1, -1)) 31 | 32 | def test_point_iops(self): 33 | p1 = APoint(1, 1, 1) 34 | p2 = APoint(2, 2, 2) 35 | p3 = APoint(3, 3, 3) 36 | 37 | p1 += 2 38 | p2 += p3 39 | self.assertEqual(p1, p3) 40 | self.assertEqual(p2, (5, 5, 5)) 41 | 42 | p1 = APoint(1, 1, 1) 43 | p2 = APoint(2, 2, 2) 44 | p1 -= 2 45 | p2 -= p3 46 | self.assertEqual(p1, (-1, -1, -1)) 47 | self.assertEqual(p2, (-1, -1, -1)) 48 | p = APoint(5, 5, 5) 49 | p /= 2.0 50 | self.assertEqual(p, (2.5, 2.5, 2.5)) 51 | p *= 2 52 | self.assertEqual(p, (5, 5, 5)) 53 | 54 | def test_args(self): 55 | wrong_args = ['123', (1,2), [1,2,3,4]] 56 | for arg in wrong_args: 57 | with self.assertRaises(TypeError): 58 | p = APoint(arg) 59 | 60 | p = APoint(0, 0, 0) 61 | for arg in wrong_args: 62 | try: 63 | p += arg 64 | self.fail('Wrong arg passed') 65 | except Exception: 66 | pass 67 | 68 | def test_attributes(self): 69 | p1 = APoint(1, 1, 1) 70 | p1.x += 1 71 | p1.y += 1 72 | p1.z += 1 73 | self.assertEqual(p1, (2, 2, 2)) 74 | 75 | def test_distance(self): 76 | p1 = APoint(10, 10, 10) 77 | p2 = APoint(15, 15, 15) 78 | self.assertAlmostEqual(p1.distance_to(p2), 8.660254037844387) 79 | self.assertEqual(distance(p1, p2), distance(p2, p1)) 80 | 81 | 82 | if __name__ == '__main__': 83 | unittest.main() 84 | -------------------------------------------------------------------------------- /tests/test_utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import unicode_literals 4 | import os, sys 5 | sys.path.insert(0, os.path.abspath('..')) 6 | import unittest 7 | 8 | from pyautocad.utils import unformat_mtext, mtext_to_string, string_to_mtext 9 | 10 | class UtilsTestCase(unittest.TestCase): 11 | def test_unformat(self): 12 | texts = [r'{\fGOST type A|b0|i0|c204|p34;ЩО\fGOST type A|b0|i0|c0|p34;2-8}', 13 | r'text\Pwith {\fWide Latin|b0|i0|c0|p18;multi} {\fVerdana|b0|i0|c0|p34;format}', 14 | r'test\P\pxi-3,l3,t3;1. list1\P2. list2\P\pi0,l0,tz;\P\pi-3,l3,t3;{\fSymbol|b0|i0|c2|p18;· }bullet1\P{\fSymbol|b0|i0|c2|p18;· }bullet2\P\pi0,l0,tz;\P{\fVani|b0|i0|c0|p34;another font} {\fVerdana|b1|i0|c0|p34;and bold}', 15 | ] 16 | desired1 = [u'ЩО2-8', u'text\Pwith multi format', u'test\P1. list1\P2. list2\P\P· bullet1\P· bullet2\P\Panother font and bold'] 17 | desired2 = [u'ЩО2-8', u'text\nwith multi format', 18 | u"""test 19 | 1. list1 20 | 2. list2 21 | 22 | · bullet1 23 | · bullet2 24 | 25 | another font and bold"""] 26 | result1 = list(map(unformat_mtext, texts)) 27 | result2 = list(map(mtext_to_string, texts)) 28 | 29 | self.assertEqual(result1, desired1) 30 | self.assertEqual(result2, desired2) 31 | 32 | def test_format(self): 33 | text = u'Line1\nLine2\nLine3\n\n\nBackslash\\ and \\P' 34 | desired = u'Line1\PLine2\PLine3\P\P\PBackslash\\\\ and \\\\P' 35 | 36 | self.assertEqual(string_to_mtext(text), desired) 37 | 38 | 39 | if __name__ == '__main__': 40 | unittest.main() 41 | --------------------------------------------------------------------------------