diff --git a/src/contrib/ccad/LICENSE b/src/contrib/ccad/LICENSE new file mode 100644 index 000000000..02bbb60bc --- /dev/null +++ b/src/contrib/ccad/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/src/contrib/ccad/MANIFEST.in b/src/contrib/ccad/MANIFEST.in new file mode 100644 index 000000000..52b401395 --- /dev/null +++ b/src/contrib/ccad/MANIFEST.in @@ -0,0 +1,8 @@ +# Base Installation +global-exclude * +include README LICENSE setup.py MANIFEST.in +include __init__.py model.py display.py +include doc/*.rst doc/*.py doc/*.png doc/*.svg +include unittest/*.py +recursive-include doc/html * # This duplicates images. I don't like that +include unittest/*.py \ No newline at end of file diff --git a/src/contrib/ccad/PKG-INFO b/src/contrib/ccad/PKG-INFO new file mode 100644 index 000000000..77e6ac84f --- /dev/null +++ b/src/contrib/ccad/PKG-INFO @@ -0,0 +1,14 @@ +Metadata-Version: 1.1 +Name: ccad +Version: 0.1 +Summary: UNKNOWN +Home-page: UNKNOWN +Author: UNKNOWN +Author-email: UNKNOWN +License: UNKNOWN +Description: UNKNOWN +Platform: UNKNOWN +Requires: occe +Requires: pythonocc +Requires: pygtk +Requires: gtkglext diff --git a/src/contrib/ccad/README b/src/contrib/ccad/README new file mode 100644 index 000000000..f45dfba27 --- /dev/null +++ b/src/contrib/ccad/README @@ -0,0 +1,33 @@ +To Install +========== + + tar xvzf ccad-ver.tar.gz (where ver is the version number) + cd ccad-ver (where ver is the version number) + python setup.py install --prefix=/usr/local (as root) + +Change the prefix argument to install in a different directory. + +To Operate +========== + +Consult the documentation at prefix/usr/local/html/contents.html + +To Build (only for developers) +============================== + +Update components with modifications to MANIFEST.in and setup.py. +Then, + + python setup.py sdist + +**Make sure you verify changes with unittest/test_all.py before +committing.** + +To Build the Documentation (only for developers) +================================================ + + cd ccad-ver/rst + python generate_images.py + sphinx-build . html + +Then, direct your browser to ccad-ver/doc/html/contents.html diff --git a/src/contrib/ccad/__init__.py b/src/contrib/ccad/__init__.py new file mode 100644 index 000000000..6e199653f --- /dev/null +++ b/src/contrib/ccad/__init__.py @@ -0,0 +1,4 @@ +# Empty + +from model import * +from display import * diff --git a/src/contrib/ccad/display.py b/src/contrib/ccad/display.py new file mode 100644 index 000000000..edfde27fb --- /dev/null +++ b/src/contrib/ccad/display.py @@ -0,0 +1,1276 @@ +""" +Description +----------- +ccad viewer designed to work under ipython or called from a python +program. View model.py for a full description of ccad. + +It is meant to handle viewers from a variety of GUIs via the view +function. Currently, however, it only supports gtk. + +Author +------ +Charles Sharman + +License +------- +Distributed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +View LICENSE for details. +""" + +import os as _os +import sys as _sys +import math as _math + +try: + import pygtk, gtk, gtk.gtkgl, gobject +except ImportError: + print "Please install python-gtk (see http://www.pygtk.org)." + _sys.exit(0) + +from OCC import (AIS as _AIS, Aspect as _Aspect, gp as _gp, + Graphic3d as _Graphic3d, Prs3d as _Prs3d, + Quantity as _Quantity, TopAbs as _TopAbs, V3d as _V3d) +from OCC.BRepTools import BRepTools_WireExplorer as _BRepTools_WireExplorer +from OCC.HLRAlgo import HLRAlgo_Projector as _HLRAlgo_Projector +from OCC.HLRBRep import (HLRBRep_Algo as _HLRBRep_Algo, + HLRBRep_HLRToShape as _HLRBRep_HLRToShape) +from OCC.TCollection import (TCollection_ExtendedString as + _TCollection_ExtendedString) +from OCC.TopExp import TopExp_Explorer as _TopExp_Explorer +from OCC.Visual3d import Visual3d_ViewOrientation as _Visual3d_ViewOrientation +from OCC.Xw import Xw_Window as _Xw_Window, Xw_WQ_3DQUALITY as _Xw_WQ_3DQUALITY + +import ccad.model as _cm + + +# Globals +version = '0.1' +interactive = True + + +class view_gtk(object): + """ + A gtk-based viewer + """ + + REGULAR_CURSOR = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) + WAIT_CURSOR = gtk.gdk.Cursor(gtk.gdk.WATCH) + + def __init__(self, perspective=False): + """ + Perspective doesn't seem to work in pythonocc ***. Don't use. + """ + + self.key_table = {'redraw()': 'KP_Page_Up', + 'orbitup()': 'KP_Up', + 'panup()': 'KP_8', + 'orbitdown()': 'KP_Down', + 'pandown()': 'KP_2', + 'orbitleft()': 'KP_Left', + 'panleft()': 'KP_4', + 'orbitright()': 'KP_Right', + 'panright()': 'KP_6', + 'rotateccw()': 'KP_Divide', + 'rotatecw()': 'KP_Multiply', + 'zoomin()': 'KP_Add', + 'zoomout()': 'KP_Subtract', + 'fit()': 'KP_Delete', + 'query()': 'q', + 'viewstandard("top")': 'KP_Home', + 'viewstandard("bottom")': 'KP_7', + 'viewstandard("front")': 'KP_End', + 'viewstandard("back")': 'KP_1', + 'viewstandard("right")': 'KP_Page_Down', + 'viewstandard("left")': 'KP_3', + 'quit()': 'q'} + + self.occ_viewer = None + self.occ_view = None + self.selected = None + self.selected_shape = None + self.selection_type = 'shape' + self.selection_index = -1 + self.foreground = (1.0, 1.0, 0.0) # Bright-Yellow is default + self.SCR = (400, 400) # initial screen size + self.display_shapes = [] + + # Main Window + self.win = gtk.Window() + self.win.set_title('ccad viewer') + self.win.connect('destroy', self.quit) + self.win.connect('key_press_event', self.keypress) + #self.win.set_resize_mode(gtk.RESIZE_IMMEDIATE) + self.win.show() + #window_handle = self.win.window.xid + #print 'window_handle', window_handle + self.menus = {} + + # Vertical Container + vbox1 = gtk.VBox() + self.win.add(vbox1) + vbox1.show() + + hbox1 = gtk.HBox(False) + hbox1.show() + vbox1.pack_start(hbox1, False) + + ## Menu Space + accel_group = gtk.AccelGroup() + self.win.add_accel_group(accel_group) + + self.menubar = gtk.MenuBar() + hbox1.pack_start(self.menubar, False) + self.menubar.show() + + ### File + + file_container = gtk.Menu() + + file_save = gtk.ImageMenuItem(gtk.STOCK_SAVE) + file_save.connect('activate', self.save) + file_container.append(file_save) + + file_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT) + file_quit.connect('activate', self.quit) + keyval, keymod = self.key_lookup('quit()') + file_quit.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + file_container.append(file_quit) + + file_menu = gtk.MenuItem('_File') + file_menu.set_submenu(file_container) + file_menu.show() + self.menubar.append(file_menu) + + ### view + view_container = gtk.Menu() + + view_mode = gtk.MenuItem('Mode') + view_mode_container = gtk.Menu() + view_mode.set_submenu(view_mode_container) + view_container.append(view_mode) + + view_wireframe = gtk.RadioMenuItem(None, 'Wireframe') + view_wireframe.connect('activate', self.mode_wireframe) + view_mode_container.append(view_wireframe) + + self.view_shaded = gtk.RadioMenuItem(view_wireframe, 'Shaded') + self.view_shaded.set_active(True) + self.view_shaded.connect('activate', self.mode_shaded) + view_mode_container.append(self.view_shaded) + + view_hlr = gtk.RadioMenuItem(self.view_shaded, 'Hidden Line Removal') + view_hlr.connect('activate', self.mode_hlr) + view_mode_container.append(view_hlr) + + #self.view_drawing = gtk.RadioMenuItem(view_hlr, 'Drawing') + #self.view_drawing.connect('activate', self.mode_drawing) + #view_mode_container.append(self.view_drawing) + + view_side = gtk.MenuItem('Side') + view_side_container = gtk.Menu() + view_side.set_submenu(view_side_container) + view_container.append(view_side) + + view_front = gtk.MenuItem('Front') + view_front.connect('activate', self.viewstandard, 'front') + keyval, keymod = self.key_lookup('viewstandard("front")') + view_front.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_side_container.append(view_front) + + view_top = gtk.MenuItem('Top') + view_top.connect('activate', self.viewstandard, 'top') + keyval, keymod = self.key_lookup('viewstandard("top")') + view_top.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_side_container.append(view_top) + + view_right = gtk.MenuItem('Right') + view_right.connect('activate', self.viewstandard, 'right') + keyval, keymod = self.key_lookup('viewstandard("right")') + view_right.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_side_container.append(view_right) + + view_back = gtk.MenuItem('Back') + view_back.connect('activate', self.viewstandard, 'back') + keyval, keymod = self.key_lookup('viewstandard("back")') + view_back.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_side_container.append(view_back) + + view_bottom = gtk.MenuItem('Bottom') + view_bottom.connect('activate', self.viewstandard, 'bottom') + keyval, keymod = self.key_lookup('viewstandard("bottom")') + view_bottom.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_side_container.append(view_bottom) + + view_left = gtk.MenuItem('Left') + view_left.connect('activate', self.viewstandard, 'left') + keyval, keymod = self.key_lookup('viewstandard("left")') + view_left.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_side_container.append(view_left) + + view_orbit = gtk.MenuItem('Orbit') + view_orbit_container = gtk.Menu() + view_orbit.set_submenu(view_orbit_container) + view_container.append(view_orbit) + + view_orbitup = gtk.MenuItem('Up') + view_orbitup.connect('activate', self.orbitup) + keyval, keymod = self.key_lookup('orbitup()') + view_orbitup.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_orbit_container.append(view_orbitup) + + view_orbitdown = gtk.MenuItem('Down') + view_orbitdown.connect('activate', self.orbitdown) + keyval, keymod = self.key_lookup('orbitdown()') + view_orbitdown.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_orbit_container.append(view_orbitdown) + + view_orbitleft = gtk.MenuItem('Left') + view_orbitleft.connect('activate', self.orbitleft) + keyval, keymod = self.key_lookup('orbitleft()') + view_orbitleft.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_orbit_container.append(view_orbitleft) + + view_orbitright = gtk.MenuItem('Right') + view_orbitright.connect('activate', self.orbitright) + keyval, keymod = self.key_lookup('orbitright()') + view_orbitright.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_orbit_container.append(view_orbitright) + + view_orbitccw = gtk.MenuItem('CCW') + view_orbitccw.connect('activate', self.rotateccw) + keyval, keymod = self.key_lookup('rotateccw()') + view_orbitccw.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_orbit_container.append(view_orbitccw) + + view_orbitcw = gtk.MenuItem('CW') + view_orbitcw.connect('activate', self.rotatecw) + keyval, keymod = self.key_lookup('rotatecw()') + view_orbitcw.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_orbit_container.append(view_orbitcw) + + view_pan = gtk.MenuItem('Pan') + view_pan_container = gtk.Menu() + view_pan.set_submenu(view_pan_container) + view_container.append(view_pan) + + view_panup = gtk.MenuItem('Up') + view_panup.connect('activate', self.panup) + keyval, keymod = self.key_lookup('panup()') + view_panup.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_pan_container.append(view_panup) + + view_pandown = gtk.MenuItem('Down') + view_pandown.connect('activate', self.pandown) + keyval, keymod = self.key_lookup('pandown()') + view_pandown.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_pan_container.append(view_pandown) + + view_panleft = gtk.MenuItem('Left') + view_panleft.connect('activate', self.panleft) + keyval, keymod = self.key_lookup('panleft()') + view_panleft.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_pan_container.append(view_panleft) + + view_panright = gtk.MenuItem('Right') + view_panright.connect('activate', self.panright) + keyval, keymod = self.key_lookup('panright()') + view_panright.add_accelerator('activate', accel_group, + keyval, keymod, gtk.ACCEL_VISIBLE) + view_pan_container.append(view_panright) + + view_zoom = gtk.MenuItem('Zoom') + view_zoom_container = gtk.Menu() + view_zoom.set_submenu(view_zoom_container) + view_container.append(view_zoom) + + view_zoomin = gtk.MenuItem('In') + view_zoomin.connect('activate', self.zoomin) + keyval, keymod = self.key_lookup('zoomin()') + view_zoomin.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_zoom_container.append(view_zoomin) + + view_zoomout = gtk.MenuItem('Out') + view_zoomout.connect('activate', self.zoomout) + keyval, keymod = self.key_lookup('zoomout()') + view_zoomout.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_zoom_container.append(view_zoomout) + + view_fit = gtk.MenuItem('Fit to Screen') + view_fit.connect('activate', self.fit) + keyval, keymod = self.key_lookup('fit()') + view_fit.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + view_zoom_container.append(view_fit) + + view_menu = gtk.MenuItem('_View') + view_menu.set_submenu(view_container) + view_menu.show() + self.menubar.append(view_menu) + + ### select + select_container = gtk.Menu() + + select_vertex = gtk.RadioMenuItem(None, 'Select Vertex') + select_vertex.connect('activate', self.select_vertex) + select_container.append(select_vertex) + + select_edge = gtk.RadioMenuItem(select_vertex, 'Select Edge') + select_edge.connect('activate', self.select_edge) + select_container.append(select_edge) + + select_wire = gtk.RadioMenuItem(select_vertex, 'Select Wire') + select_wire.connect('activate', self.select_wire) + select_container.append(select_wire) + + select_face = gtk.RadioMenuItem(select_edge, 'Select Face') + select_face.connect('activate', self.select_face) + select_container.append(select_face) + + select_shape = gtk.RadioMenuItem(select_face, 'Select Shape') + select_shape.set_active(True) + select_shape.connect('activate', self.select_shape) + select_container.append(select_shape) + + select_query = gtk.MenuItem('Query') + select_query.connect('activate', self.query) + keyval, keymod = self.key_lookup('query()') + select_query.add_accelerator('activate', accel_group, keyval, + keymod, gtk.ACCEL_VISIBLE) + select_container.append(select_query) + + select_menu = gtk.MenuItem('_Select') + select_menu.set_submenu(select_container) + select_menu.show() + self.menubar.append(select_menu) + + ### help + help_container = gtk.Menu() + + help_manual = gtk.ImageMenuItem('_Manual') + help_manual.set_image(gtk.image_new_from_stock(gtk.STOCK_HELP, gtk.ICON_SIZE_MENU)) + help_manual.connect('activate', self.display_manual) + help_container.append(help_manual) + + help_about = gtk.ImageMenuItem(gtk.STOCK_ABOUT) + help_about.connect('activate', self.about) + help_container.append(help_about) + + help_menu = gtk.MenuItem('_Help') + help_menu.set_submenu(help_container) + help_menu.show() + self.menubar.append(help_menu) + + self.menubar.show_all() + + # OpenGL Space + glconfig = gtk.gdkgl.Config(mode=gtk.gdkgl.MODE_RGBA | gtk.gdkgl.MODE_DEPTH | gtk.gdkgl.MODE_DOUBLE) + self.glarea = gtk.gtkgl.DrawingArea(glconfig) + self.glarea.set_size_request(self.SCR[0], self.SCR[1]) + # Set up the events + self.glarea.connect_after('realize', self.realize) + self.glarea.connect('configure-event', self.resize) + self.glarea.connect('expose-event', self.draw) + self.glarea.connect('map-event', self.draw) + self.glarea.connect('button_press_event', self.mousepress) + self.glarea.connect('button_release_event', self.mouserelease) + self.glarea.connect('motion_notify_event', self.mousemotion) + self.glarea.add_events(gtk.gdk.BUTTON_PRESS_MASK | gtk.gdk.BUTTON_RELEASE_MASK | gtk.gdk.POINTER_MOTION_MASK) + gtk.gtkgl.widget_set_gl_capability(self.glarea, glconfig) + self.glarea.show() + vbox1.pack_start(self.glarea) + + # Status Line + hbox_status = gtk.HBox() + hbox_status.show() + vbox1.pack_start(hbox_status, False, False) + + self.status_bar = gtk.Label() + self.status_bar.set_text('') + self.status_bar.show() + hbox_status.pack_start(self.status_bar, False, False) + + # Set up the OCC hooks to the OpenGL space + # Get the handle IMMEDIATELY after creating an object. + # Otherwise, this shows very strange behavior (usually seg + # faults!) + window_handle = self.glarea.window.xid + + gd = _Graphic3d.Graphic3d_GraphicDevice(_os.environ['DISPLAY']) + window = _Xw_Window(gd.GetHandle(), window_handle >> 16, window_handle & 0xffff, _Xw_WQ_3DQUALITY) + self.occ_viewer = _V3d.V3d_Viewer(gd.GetHandle(), _TCollection_ExtendedString('Viewer').ToExtString()) + + # This works now, by reading the handle first. Go figure . . . + #print 'Viewer created' + handle_occ_viewer = self.occ_viewer.GetHandle() + self.occ_viewer.Init() + #print 'Viewer inited" + #self.handle_view = self.occ_viewer.CreateView() + if perspective: + self.handle_view = self.occ_viewer.DefaultPerspectiveView() + else: + self.handle_view = self.occ_viewer.DefaultOrthographicView() + #print 'View handle created" + self.occ_view = self.handle_view.GetObject() + #print 'view created' + self.occ_view.SetWindow(window.GetHandle()) + #print 'View window set' + + if not window.IsMapped(): + window.Map() + self.occ_context = _AIS.AIS_InteractiveContext(handle_occ_viewer) + handle_occ_context = self.occ_context.GetHandle() + + # Some Initial Values + self.occ_view.SetComputedMode(False) + self.mode_shaded() + self.occ_view.SetBackgroundColor(_Quantity.Quantity_TOC_RGB, 0.0, 0.0, 0.0) + self.set_triedron(1) + + self.occ_view.MustBeResized() + + # Set up some initial states + self.morbit = _math.pi/12.0 + self.set_scale(10.0) + + + def add_menu(self, hierarchy): + """ + Add a menu. Used internally. Used externally for those who + know the window manager well, and want to add fancy menu items + (radio buttons, check boxes, items with graphics, etc.) + manually. For text-only menu items, use add_menuitem. + """ + last_menu = (None, self.menubar) + for sub_menu in hierarchy: + if sub_menu not in self.menus: + menu = gtk.MenuItem(sub_menu) + menu_container = gtk.Menu() + menu.set_submenu(menu_container) + menu.show() + last_menu[1].append(menu) + self.menus[sub_menu] = (menu, menu_container) + last_menu = self.menus[sub_menu] + return last_menu + + + def add_menuitem(self, hierarchy, func, *args): + """ + Add a menu item. hierarchy is a tuple. The first element in + the tuple is the main menu at the menubar level, and the last + item is the menu item. Intermediate items may be specified, + if there are submenus. func is the function to call when the + menu is selected. args are any pass parameters to pass to the + function. + + Generates all needed menus to create the passed hierarchy. + """ + last_menu = self.add_menu(hierarchy[:-1]) + menuitem = gtk.MenuItem(hierarchy[-1]) + menuitem.connect('activate', func, *args) + menuitem.show() + last_menu[1].append(menuitem) + + + # Event Functions + def realize(self, widget): + """ + Called on a window realize + """ + pass # Adding the glcontext checking here didn't help + + + def resize(self, widget, event): + """ + Called on a window resize + """ + #print 'Resize called' + + glcontext = gtk.gtkgl.widget_get_gl_context(self.glarea) + gldrawable = gtk.gtkgl.widget_get_gl_drawable(self.glarea) + if not gldrawable.gl_begin(glcontext): + return + + w = widget.allocation.width + h = widget.allocation.height + + self.SCR = (w, h) + self.mpan = max(w, h) / 10 + + if self.occ_view: + #print 'Resize' + self.occ_view.MustBeResized() + + gldrawable.gl_end() + + + def draw(self, widget, event): + """ + Called on a window draw + """ + #print 'Draw called' + + # For some reason, this check made the display blank on init + #glcontext = gtk.gtkgl.widget_get_gl_context(self.glarea) + #gldrawable = gtk.gtkgl.widget_get_gl_drawable(self.glarea) + #if not gldrawable.gl_begin(glcontext):return + + if self.occ_viewer: + #print 'Draw' + self.occ_viewer.Redraw() + + #gldrawable.gl_end() + + + def redraw(self, widget=None): + """ + Called from a user request + """ + self.glarea.queue_draw() + + + def key_lookup(self, func_call): + """ + Connects a key press to a function in the key_table + """ + key = self.key_table[func_call] + retval = gtk.accelerator_parse(key) + #print key + if key not in ['KP_Up', 'KP_Down', 'KP_Left', 'KP_Right', 'KP_Delete']: # Avoids pygtk bug that kind of and kind of doesn't recognize these keys + #del self.key_table[key] + del self.key_table[func_call] + return retval + + + def keypress(self, widget, event): + """ + Called when a key is pressed + """ + key = gtk.accelerator_name(event.keyval, event.state & gtk.gdk.MODIFIER_MASK) + self.status_bar.set_text('Key ' + key) + if key in self.key_table.values(): + try: + cmd = self.key_table.keys()[self.key_table.values().index(key)] + eval('self.' + cmd) + except: + self.status_bar.set_text('Command unknown ' + cmd) + + + def mousepress(self, widget, event): + """ + Called when a mouse button is pressed + """ + self.beginx, self.beginy = int(event.x), int(event.y) + self.occ_view.StartRotation(self.beginx, self.beginy) + + + def mouserelease(self, widget, event): + """ + Called when a mouse button is released + """ + x, y = int(event.x), int(event.y) + if event.state & gtk.gdk.BUTTON3_MASK: # Selection + self.occ_context.Select() + self.occ_context.InitSelected() + if self.occ_context.MoreSelected(): + if self.occ_context.HasSelectedShape(): + self.selected = self.occ_context.SelectedShape() + #print "Current selection (single):",self.selected_shape + else: + self.selected = None + self.make_selection() + + + def mousemotion(self, widget, event): + """ + Called when a mouse button is pressed and the mouse is moving + """ + x, y = int(event.x), int(event.y) + # Mouse-Controlled Projection + # ComputedModes are too slow to redraw, so disabled for them + if (event.state & gtk.gdk.BUTTON2_MASK) and \ + not self.occ_view.ComputedMode(): + if event.state & gtk.gdk.SHIFT_MASK: # Mouse-Controlled Pan + self.occ_view.Pan(x - self.beginx, -y + self.beginy) + self.beginx, self.beginy = x, y + else: # Mouse-Controlled Orbit + self.occ_view.Rotation(x, y) + self.occ_context.MoveTo(x, y, self.handle_view) + + + # View Functions + def viewstandard(self, widget=None, viewtype='front'): + """ + Sets up the viewing projection according to a standard set of views + """ + if viewtype == 'front': + self.occ_view.SetProj(_V3d.V3d_Yneg) + elif viewtype == 'back': + self.occ_view.SetProj(_V3d.V3d_Ypos) + elif viewtype == 'top': + self.occ_view.SetProj(_V3d.V3d_Zpos) + elif viewtype == 'bottom': + self.occ_view.SetProj(_V3d.V3d_Zneg) + elif viewtype == 'right': + self.occ_view.SetProj(_V3d.V3d_Xpos) + elif viewtype == 'left': + self.occ_view.SetProj(_V3d.V3d_Xneg) + elif viewtype == 'iso': + self.occ_view.SetProj(_V3d.V3d_XposYnegZpos) + elif viewtype == 'iso_back': + self.occ_view.SetProj(_V3d.V3d_XnegYposZneg) + else: + self.status_bar.set_text('Unknown view' + viewtype) + + + def orbitup(self, widget=None, rapid=False): + """ + The observer has moved up + + All orbits orbit with respect to (0,0,0). That means points + far from (0,0,0) will translate as you orbit. I'd prefer it + orbiting with respect to the center of the screen. That + should be possible using the Gravity method from occ_view, but + pythonocc doesn't implement OCC's Gravity. + """ + # The better way + #gravity = self.occ_view.Gravity() # pythonocc doesn't implement + #self.occ_view.Rotate(0.0, -self.morbit, 0.0, gravity[0], gravity[1], gravity[2]) + self.occ_view.Rotate(0.0, -self.morbit, 0.0) + + + def panup(self, widget=None, rapid=False): + """ + The scene is panned up + """ + self.occ_view.Pan(0, -self.mpan) + + + def orbitdown(self, widget=None, rapid=False): + """ + The observer has moved down + """ + self.occ_view.Rotate(0.0, self.morbit, 0.0) + + + def pandown(self, widget=None, rapid=False): + """ + The scene is panned down + """ + self.occ_view.Pan(0, self.mpan) + + + def orbitright(self, widget = None, rapid=False): + """ + The observer has moved to the right + """ + self.occ_view.Rotate(-self.morbit, 0.0, 0.0) + + + def panright(self, widget=None, rapid=False): + """ + The scene is panned right + """ + self.occ_view.Pan(-self.mpan, 0) + + + def orbitleft(self, widget=None, rapid=False): + """ + The observer has moved to the left + """ + self.occ_view.Rotate(self.morbit, 0.0, 0.0) + + + def panleft(self, widget=None, rapid=False): + """ + The scene is panned to the left + """ + self.occ_view.Pan(self.mpan, 0) + + + def zoomin(self, widget=None, rapid=False): + """ + Zoom in + """ + self.occ_view.SetZoom(_math.sqrt(2.0)) + + + def zoomout(self, widget=None, rapid=False): + """ + Zoom out + """ + self.occ_view.SetZoom(_math.sqrt(0.5)) + + + def rotateccw(self, widget=None, rapid=False): + """ + The scene is rotated counter clockwise + """ + self.occ_view.Rotate(0.0, 0.0, -self.morbit) + + + def rotatecw(self, widget=None, rapid=False): + """ + The scene is rotated clockwise + """ + self.occ_view.Rotate(0.0, 0.0, self.morbit) + + + def fit(self, widget=None): + """ + Fit the scene to the screen + """ + self.occ_view.ZFitAll() + self.occ_view.FitAll() + + + def query(self, widget=None): + """ + Reports the properties of a selection + Should do something other than print (popup?) *** + """ + if self.selected is not None: + if self.selection_type == 'vertex': + s = _cm.vertex(self.selected) + retval = 'center' + s.center() + '\ntolerance' + s.tolerance() + elif self.selection_type == 'edge': + s = _cm.vertex(self.selected) + retval = 'center' + s.center() + '\ntolerance', + s.tolerance() + 'length' + s.length() + elif self.selection_type == 'wire': + s = _cm.face(self.selected) + retval = 'center' + s.center() + 'length' + s.length() + elif self.selection_type == 'face': + s = _cm.face(self.selected) + retval = 'center' + s.center() + '\ntolerance', + s.tolerance() + '\ntype', + s.type() + 'area' + s.area() + else: + retval = 'No properties for type ' + self.selection_type + print retval + + + # Direct Call (not from GUI) Functions + def set_projection(self, vcenter, vout, vup): + """ + Set the projection to a custom view given + + vcenter, the scene coordinates in the center of the window, + vout, the vector from vcenter in scene coordinates out of the window, + vup, the vector from vcenter in scene coordinates that show straight up + """ + + projection = _Visual3d_ViewOrientation(_Graphic3d.Graphic3d_Vertex(vcenter[0], vcenter[1], vcenter[2]), _Graphic3d.Graphic3d_Vector(vout[0], vout[1], vout[2]), _Graphic3d.Graphic3d_Vector(vup[0], vup[1], vup[2])) + self.occ_view.SetViewOrientation(projection) + + + def set_scale(self, scale): + """ + Set the screen scale. I'm not certain, but it looks to me + like scale is the number of scene-coordinates in the + x-direction. For example, if you have a block 8.0 wide in the + x-direction, and you set the scale to 8.0, the block will + exactly fill the screen in the x-direction. + """ + self.occ_view.SetSize(scale) + + + def set_size(self, size): + """ + Sets the size of the window in pixels. Size is a 2-tuple + """ + self.win.resize(max(1, size[0]-1), max(1, size[1]-1)) + self.glarea.set_size_request(size[0], size[1]) + + + def set_background(self, color): + """ + Sets the background color. + color is a 3-tuple with each value from 0.0 to 1.0 + """ + self.occ_view.SetBackgroundColor(_Quantity.Quantity_TOC_RGB, color[0], color[1], color[2]) + + + def set_foreground(self, color): + """ + Sets the default shape color. + color is a 3-tuple with each value from 0.0 to 1.0 + """ + self.foreground = color + + + def set_triedron(self, state, position='down_right', color=(1.0, 1.0, 1.0), size=0.08): + """ + Controls the triedron, the little x, y, z coordinate display. + + state (1 or 0) turns it on or off + position sets the position of the triedron in the window. + color sets the triedron color (only black or white, currently) + size sets the triedron size in scene-coordinates + """ + if not state: + self.occ_view.TriedronErase() + else: + local_position = {'down_right': _Aspect.Aspect_TOTP_RIGHT_LOWER, + 'down_left': _Aspect.Aspect_TOTP_LEFT_LOWER, + 'up_right': _Aspect.Aspect_TOTP_RIGHT_UPPER, + 'up_left': _Aspect.Aspect_TOTP_LEFT_UPPER}[position] + # Can't set Triedron color RGB-wise! + #qcolor = Quantity_Color(color[0], color[1], color[2], Quantity_TOC_RGB) + if color == (1.0, 1.0, 1.0): + qcolor = _Quantity.Quantity_NOC_WHITE + else: + qcolor = _Quantity.Quantity_NOC_BLACK + self.occ_view.TriedronDisplay(local_position, qcolor, size, _V3d.V3d_WIREFRAME) + + + # Things to Show Functions + def display(self, shape, color=None, material='default', transparency=0.0, line_type='solid', line_width=1, logging=True): + """ + Displays a ccad shape. + + color is used for all shape types. It is a tuple of (R, G, B) + from 0.0 to 1.0. + + material sets the solid material (unused for non-solids). + Material can be: + + brass + bronze + copper + gold + pewter + plaster + plastic + silver + steel + stone + shiny_plastic + satin + metallized + neon_gnc + chrome + aluminum + obsidian + neon_phc + jade + default + + transparency sets the solid transparency; 0 is opaque; 1 is + transparent + + line_type can be solid, dash, or dot for edges and wires + + line_width sets the edge or wire width in pixels + + logging allows you to keep a list of all shapes displayed + """ + if hasattr(shape, 'shape'): + s = shape.shape + else: + s = shape + self.selected_shape = s + display_shape = {'shape': s, + 'color': color, + 'material': material, + 'transparency': transparency, + 'line_type': line_type, + 'line_width': line_width} + if logging: + self.display_shapes.append(display_shape) + aisshape = _AIS.AIS_Shape(s) + handle_aisshape = aisshape.GetHandle() + + # Set Color + if not color: + color = self.foreground + #print 'color', color + + #drawer = AIS_Drawer() + #handle_drawer = drawer.GetHandle() + + handle_drawer = aisshape.Attributes() + drawer = handle_drawer.GetObject() + + qcolor = _Quantity.Quantity_Color(color[0], color[1], color[2], _Quantity.Quantity_TOC_RGB) + + # Set Point Type + aspect_point = _Prs3d.Prs3d_PointAspect(_Aspect.Aspect_TOM_PLUS, qcolor, 1.0) + handle_aspect_point = aspect_point.GetHandle() + drawer.SetPointAspect(handle_aspect_point) + + # Set Line Type + local_line_type = {'solid': _Aspect.Aspect_TOL_SOLID, + 'dash': _Aspect.Aspect_TOL_DASH, + 'dot': _Aspect.Aspect_TOL_DOT}[line_type] + aspect_line = _Prs3d.Prs3d_LineAspect(qcolor, local_line_type, line_width) + handle_aspect_line = aspect_line.GetHandle() + #drawer = self.occ_context.DefaultDrawer().GetObject() + drawer.SetSeenLineAspect(handle_aspect_line) + drawer.SetWireAspect(handle_aspect_line) + + # Set Shading Type + aspect_shading = _Prs3d.Prs3d_ShadingAspect() + handle_aspect_shading = aspect_shading.GetHandle() + #print 'shading color', color + aspect_shading.SetColor(qcolor, _Aspect.Aspect_TOFM_BOTH_SIDE) + local_material = {'brass': _Graphic3d.Graphic3d_NOM_BRASS, + 'bronze': _Graphic3d.Graphic3d_NOM_BRONZE, + 'copper': _Graphic3d.Graphic3d_NOM_COPPER, + 'gold': _Graphic3d.Graphic3d_NOM_GOLD, + 'pewter': _Graphic3d.Graphic3d_NOM_PEWTER, + 'plaster': _Graphic3d.Graphic3d_NOM_PLASTER, + 'plastic': _Graphic3d.Graphic3d_NOM_PLASTIC, + 'silver': _Graphic3d.Graphic3d_NOM_SILVER, + 'steel': _Graphic3d.Graphic3d_NOM_STEEL, + 'stone': _Graphic3d.Graphic3d_NOM_STONE, + 'shiny_plastic': _Graphic3d.Graphic3d_NOM_SHINY_PLASTIC, + 'satin': _Graphic3d.Graphic3d_NOM_SATIN, + 'metallized': _Graphic3d.Graphic3d_NOM_METALIZED, + 'neon_gnc': _Graphic3d.Graphic3d_NOM_NEON_GNC, + 'chrome': _Graphic3d.Graphic3d_NOM_CHROME, + 'aluminum': _Graphic3d.Graphic3d_NOM_ALUMINIUM, + 'obsidian': _Graphic3d.Graphic3d_NOM_OBSIDIAN, + 'neon_phc': _Graphic3d.Graphic3d_NOM_NEON_PHC, + 'jade': _Graphic3d.Graphic3d_NOM_JADE, + 'default': _Graphic3d.Graphic3d_NOM_DEFAULT}[material] + aspect_shading.SetMaterial(local_material) + aspect_shading.SetTransparency(transparency) + drawer.SetShadingAspect(handle_aspect_shading) + + self.occ_context.Display(handle_aisshape, True) + + + def clear(self, display_shapes=True): + """ + Clears all shapes from the window + """ + self.select_shape() + self.occ_context.PurgeDisplay() + self.occ_context.EraseAll() + if display_shapes: + self.display_shapes = [] + + + # Selection Functions + def _build_hashes(self, htype): + if htype == 'face': + ex_type = _TopAbs.TopAbs_FACE + elif htype == 'wire': + ex_type = _TopAbs.TopAbs_WIRE + elif htype == 'edge': + ex_type = _TopAbs.TopAbs_EDGE + elif htype == 'vertex': + ex_type = _TopAbs.TopAbs_VERTEX + else: + print 'Error: Unknown hash type', htype + if self.selected_shape.ShapeType == _TopAbs.TopAbs_WIRE and htype == 'edge': + ex = _BRepTools_WireExplorer(selected_shape) # Ordered this way + else: + ex = _TopExp_Explorer(self.selected_shape, ex_type) + self.hashes = [] + self.positions = [] + while ex.More(): + s1 = ex.Current() + # Calculate hash + s1_hash = s1.__hash__() + if s1_hash not in self.hashes: + self.hashes.append(s1_hash) + # Calculate position + if htype == 'face': + f = _cm.face(s1) + c = (' type ' + f.type(), f.center()) + elif htype == 'wire': + w = _cm.wire(s1) + c = ('', w.center()) + elif htype == 'edge': + e = _cm.edge(s1) + c = ('', e.center()) + elif htype == 'vertex': + c = ('', _cm.vertex(s1).center()) + self.positions.append(c) + ex.Next() + + + def make_selection(self, event=None): + """ + Called when a shape is selected + """ + if self.selected is not None: + if self.selection_type == 'shape': + self.selected_shape = self.selected + else: + h = self.selected.__hash__() + try: + index = self.hashes.index(h) + except ValueError: + index = -1 + if index == -1: + self.status_bar.set_text('Select shape first.') + else: + status = self.selection_type + ' ' + str(index) + self.positions[index][0] + ' at (%.9f, %.9f, %.9f)' % self.positions[index][1] + print status + self.status_bar.set_text(status) + self.selection_index = index + + + def select_vertex(self, event=None): + """ + Changes to a mode where only vertices can be selected + """ + self.glarea.window.set_cursor(self.WAIT_CURSOR) + self.occ_context.CloseAllContexts() + self.occ_context.OpenLocalContext() + self.occ_context.ActivateStandardMode(_TopAbs.TopAbs_VERTEX) + self._build_hashes('vertex') + self.selection_type = 'vertex' + self.glarea.window.set_cursor(self.REGULAR_CURSOR) + + + def select_edge(self, event=None): + """ + Changes to a mode where only edges can be selected + """ + self.glarea.window.set_cursor(self.WAIT_CURSOR) + self.occ_context.CloseAllContexts() + self.occ_context.OpenLocalContext() + self.occ_context.ActivateStandardMode(_TopAbs.TopAbs_EDGE) + self._build_hashes('edge') + self.selection_type = 'edge' + self.glarea.window.set_cursor(self.REGULAR_CURSOR) + + + def select_wire(self, event=None): + """ + Changes to a mode where only wires can be selected + """ + self.glarea.window.set_cursor(self.WAIT_CURSOR) + self.occ_context.CloseAllContexts() + self.occ_context.OpenLocalContext() + self.occ_context.ActivateStandardMode(_TopAbs.TopAbs_WIRE) + self._build_hashes('wire') + self.selection_type = 'wire' + self.glarea.window.set_cursor(self.REGULAR_CURSOR) + + + def select_face(self, event=None): + """ + Changes to a mode where only faces can be selected + """ + self.glarea.window.set_cursor(self.WAIT_CURSOR) + self.occ_context.CloseAllContexts() + self.occ_context.OpenLocalContext() + self.occ_context.ActivateStandardMode(_TopAbs.TopAbs_FACE) + self._build_hashes('face') + self.selection_type = 'face' + self.glarea.window.set_cursor(self.REGULAR_CURSOR) + + + def select_shape(self, event=None): + """ + Changes to a mode where only shapes can be selected + """ + self.glarea.window.set_cursor(self.WAIT_CURSOR) + self.occ_context.CloseAllContexts() + self.selection_type = 'shape' + self.glarea.window.set_cursor(self.REGULAR_CURSOR) + + + # Viewing Mode Functions + def mode_wireframe(self, widget=None): + """ + Changes the display to view shapes as wireframes + """ + if not widget or (widget and widget.get_active()): + self.occ_view.SetComputedMode(False) + self.occ_context.SetDisplayMode(_AIS.AIS_WireFrame) + + + def mode_shaded(self, widget=None): + """ + Changes the display to view shapes as shaded (filled) shapes. + """ + if not widget or (widget and widget.get_active()): + self.occ_view.SetComputedMode(False) + self.occ_context.SetDisplayMode(_AIS.AIS_Shaded) + + + def mode_hlr(self, widget=None): + """ + Changes the display to view shapes in hidden line removal + mode, where the part outline, sharp edges, and face barriers + are shown as lines. + """ + if not widget or (widget and widget.get_active()): + self.occ_view.SetComputedMode(True) + self.occ_context.SetDisplayMode(_AIS.AIS_ExactHLR) + + # Draws hidden lines + #presentation = Prs3d_LineAspect(Quantity_NOC_BLACK, Aspect_TOL_DASH, 3) + #self.occ_context.SetHiddenLineAspect(presentation.GetHandle()) + #self.occ_context.EnableDrawHiddenLine() + + + def reset_mode_drawing(self): + """ + Call this after mode_drawing to reset everything. + """ + self.view_shaded.set_active(True) + self.clear(0) + self.occ_view.SetViewOrientation(self.saved_projection) + for display_shape in self.display_shapes: + self.display(display_shape['shape'], display_shape['color'], display_shape['material'], display_shape['transparency'], display_shape['line_type'], display_shape['line_width'], logging = 0) + + + def mode_drawing(self, widget=None): + """ + This is a stand-alone call to make a drafting-like drawing of + the shape. It's better than HLR, because HLR shows creases at + edges where shapes are tangent. If this must be a menu call, + pop up a separate window for it. + """ + self.saved_projection = self.occ_view.ViewOrientation() + vcenter = self.saved_projection.ViewReferencePoint() # Graphic3d_Vertex + vout = self.saved_projection.ViewReferencePlane() # Graphic3d_Vector + vup = self.saved_projection.ViewReferenceUp() # Graphic3d_Vector + vout_gp = _gp.gp_Vec(vout.X(), vout.Y(), vout.Z()) + vright = _gp.gp_Vec(vup.X(), vup.Y(), vup.Z()) + vright.Cross(vout_gp) + projection = _HLRAlgo_Projector(_gp.gp_Ax2(_gp.gp_Pnt(vcenter.X(), vcenter.Y(), vcenter.Z()), _gp.gp_Dir(vout.X(), vout.Y(), vout.Z()), _gp.gp_Dir(vright.X(), vright.Y(), vright.Z()))) + hlr_algo = _HLRBRep_Algo() + handle_hlr_algo = hlr_algo.GetHandle() + for display_shape in self.display_shapes: + hlr_algo.Add(display_shape['shape']) + hlr_algo.Projector(projection) + hlr_algo.Update() + hlr_algo.Hide() + hlr_toshape = _HLRBRep_HLRToShape(handle_hlr_algo) + vcompound = hlr_toshape.VCompound() + outlinevcompound = hlr_toshape.OutLineVCompound() + self.clear(0) + self.display(vcompound, color=display_shape['color'], line_type=display_shape['line_type'], line_width=display_shape['line_width'], logging=False) + self.display(outlinevcompound, color=display_shape['color'], line_type=display_shape['line_type'], line_width=display_shape['line_width'], logging=False) + self.viewstandard(viewtype='top') + + + # Helps + def display_manual(self, widget): + pass + + + def about(self, widget): + global version + dialog = gtk.AboutDialog() + dialog.set_name('ccad viewer') + dialog.set_version(str(version)) + dialog.set_copyright('\302\251 Copyright 2013 Seven:Twelve Engineering, LLC') + dialog.run() + dialog.destroy() + + + def save(self, name=''): + """ + Saves a screen shot + """ + + if isinstance(name, str): + filename = name + else: + dialog = gtk.FileChooserDialog(title=None, + action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons=(gtk.STOCK_CANCEL, + gtk.RESPONSE_CANCEL, + gtk.STOCK_SAVE, + gtk.RESPONSE_OK)) + for file_type in ['png', 'bmp', 'jpg', 'gif']: + filter = gtk.FileFilter() + filter.set_name(file_type) + filter.add_pattern('*.' + file_type) + dialog.add_filter(filter) + + response = dialog.run() + if response == gtk.RESPONSE_OK: + filename = dialog.get_filename() + else: + filename = '' + dialog.destroy() + + if filename: + + while gtk.events_pending(): + gtk.main_iteration() + retval = self.occ_view.Dump(filename) + if not retval: + self.status_bar.set_text('Error: Couldn\'t save ' + filename) + else: + self.status_bar.set_text('Saved ' + filename) + + # This works and allows higher resolutions + #pixmap = Image_AlienPixMap() + #self.occ_view.ToPixMap(pixmap, self.SCR[0], self.SCR[1]) + #pixmap.Save(TCollection_AsciiString(name) + + + def perspective_length(self, distance): + """ + Sets the focal length for perspective views + """ + self.occ_view.SetFocale(distance) + + + def perspective_angle(self, angle): + """ + Sets the focal length for perspective views + """ + self.occ_view.SetAngle(angle) + + + def quit(self, widget=None): + """ + Closes the viewer + """ + global __name__ + if __name__ == '__main__' or not interactive: + gtk.main_quit() + else: + self.win.destroy() + + +def view(manager='gtk', perspective=False): + if manager == 'gtk': + v1 = view_gtk(perspective) + return v1 + else: + print 'Error: Manager', manager, 'not supported' + _sys.exit() + + +def start(): # For non-interactive sessions (don't run in ipython) + global interactive + interactive = False + gtk.main() + + +if __name__ == '__main__': + import model as cm + view = view() + view.set_background((0.35, 0.35, 0.35)) + s1 = cm.sphere(1.0) + view.display(s1, (0.5, 0.0, 0.0), line_type='solid', line_width=3) + s2 = cm.box(1, 2, 3) + view.display(s2, (0.0, 0.0, 0.5), transparency=0.5, line_type='dash', line_width=1) + start() diff --git a/src/contrib/ccad/doc/all_bricks.py b/src/contrib/ccad/doc/all_bricks.py new file mode 100644 index 000000000..fe01a8dbd --- /dev/null +++ b/src/contrib/ccad/doc/all_bricks.py @@ -0,0 +1,24 @@ + +import ccad +import brick + +sizes = [(1, 1), + (2, 1), + (4, 1), + (6, 1), + (8, 1), + (2, 2), + (2, 4), + (2, 8), + (4, 4)] + +def display_brick(widget, view, size): + view.clear() + view.display(brick.brick(size[0], size[1])) + +if __name__ == '__main__': + view1 = ccad.view() + for size in sizes: + name = 'brick' + str(size[0]) + 'x' + str(size[1]) + view1.add_menuitem(('Bricks', name), display_brick, view1, size) + ccad.start() diff --git a/src/contrib/ccad/doc/boolean.rst b/src/contrib/ccad/doc/boolean.rst new file mode 100644 index 000000000..abec3178e --- /dev/null +++ b/src/contrib/ccad/doc/boolean.rst @@ -0,0 +1,97 @@ +Boolean Operations +================== + +Boolean operations operate on two solids. The following shapes are +used in the following examples:: + + s1 = ccad.sphere(1.0) + s2 = ccad.box(1.0, 2.0, 3.0) + +**s3** is the derived shape. + +Fuse +---- + +fuse fuses two solids together. Either one of the two lines below +fuses **s1** and **s2**:: + + s3 = s1 + s2 + +or:: + + s3 = ccad.fuse(s1, s2) + +.. image:: boolean_fuse.png + +Cut +--- + +cut cuts one solid from the other. Either one of the two lines below +cuts **s2** from **s1**:: + + s3 = s1 - s2 + +or:: + + s3 = ccad.cut(s1, s2) + +.. image:: boolean_cut.png + +Common +------ + +common finds the common portions of each solid. Either one of the two +lines below finds the common portions of **s1** and **s2**:: + + s3 = s1 & s2 + +or:: + + s3 = ccad.common(s1, s2) + +.. image:: boolean_common.png + +Glue +---- + +Boolean operations can have trouble when two solids have coincident +faces. Sometimes the glue function can help. glue merges two solids +at a coincident face. Unfortunately, you need to know the face +indices that must merge. Read more about finding face indices in the +Display or Logging sections:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.box(1.0, 1.0, 1.0) + s2.translate((1.0, 0.5, 1.0)) + s3 = ccad.glue(s1, s2, [(1, 0)]) + +.. image:: boolean_glue.png + +A more robust glue called **simple_glue** exists in the API, which +isn't part of OCC. For this form, however, the faces to be glued must +exactly overlap. + +Notes +----- + +Functions to use OCC's old fuse, cut, and common algorithms, and +functions to perform a fillet or chamfer on the newly created edges +after a boolean operation are also available. I use them rarely. See +the API. + +Sometimes OCC has trouble with booleans. I found the following helpful: + + - Eliminate unneeded edges. OCC's boolean operations often return + two faces in the same domain with an edge between them that can be + merged. Eliminating these edges by merging the faces help + subsequent boolean operations. The *simplify* method can do this + for some shapes. + + - Move problem edges out of the way. Cylinder and sphere edges are + necessary in OCC, but their position can often be rotated away + from a problem boolean location. + + - Slice the object (with a box or something) into sections. Then + fuse those sections back together. Then apply the boolean + operation. This was particularly helpful for creating pockets in + a solid. diff --git a/src/contrib/ccad/doc/boolean_common.png b/src/contrib/ccad/doc/boolean_common.png new file mode 100644 index 000000000..0bfc834c1 Binary files /dev/null and b/src/contrib/ccad/doc/boolean_common.png differ diff --git a/src/contrib/ccad/doc/boolean_cut.png b/src/contrib/ccad/doc/boolean_cut.png new file mode 100644 index 000000000..cf6b20024 Binary files /dev/null and b/src/contrib/ccad/doc/boolean_cut.png differ diff --git a/src/contrib/ccad/doc/boolean_fuse.png b/src/contrib/ccad/doc/boolean_fuse.png new file mode 100644 index 000000000..79e74de53 Binary files /dev/null and b/src/contrib/ccad/doc/boolean_fuse.png differ diff --git a/src/contrib/ccad/doc/boolean_glue.png b/src/contrib/ccad/doc/boolean_glue.png new file mode 100644 index 000000000..bc6dc1309 Binary files /dev/null and b/src/contrib/ccad/doc/boolean_glue.png differ diff --git a/src/contrib/ccad/doc/brick.py b/src/contrib/ccad/doc/brick.py new file mode 100644 index 000000000..0a4233b62 --- /dev/null +++ b/src/contrib/ccad/doc/brick.py @@ -0,0 +1,86 @@ +import math +import ccad + +def solidbrick(xsize, ysize, wall_offset, unit, height, draft, + knob_rad, knob_height, knob_draft): + + dx = height * math.tan(math.radians(draft)) + wbottom = ccad.rectangle(unit * xsize - 2*wall_offset, + unit * ysize - 2*wall_offset) + wtop = ccad.rectangle(unit * xsize - 2*dx - 2*wall_offset, + unit * ysize - 2*dx - 2*wall_offset) + wtop.translate((dx, dx, height)) + brick = ccad.loft([wbottom, wtop], True) + brick.translate((wall_offset, wall_offset, 0.0)) + + drad = knob_height * math.tan(math.radians(knob_draft)) + knob_top_rad = knob_rad - drad + knob_base_rad = knob_rad + drad + knob = ccad.cone(knob_base_rad, knob_top_rad, 2 * knob_height) + knob.translate((0.5 * unit, 0.5 * unit, height - knob_height)) + + for x in range(xsize): + for y in range(ysize): + brick = brick + ccad.translated(knob, (x * unit, y * unit, 0.0)) + + return brick + +def brick(xsize, ysize, fillet = True): + + unit = 5.0 + height = 10.0 + draft = 1.0 # degrees of draft on faces for plastic ejection + knob_rad = 1.8 # radius of the brick knob for mating with other bricks + knob_height = 2.0 + knob_draft = 5.0 # degrees of draft for the knob + wall_thickness = 1.0 # plastic wall thickness + fillet_rad = 0.4 # the default radius to use for rounded edges + + outerbrick = solidbrick(xsize, ysize, 0.0, unit, height, draft, + knob_rad, knob_height, knob_draft) + + if fillet: + to_fillet = [] + for count, edge_center in enumerate(outerbrick.subcenters('edge')): + if (abs(edge_center[2]) < 0.1 or + (abs(edge_center[2] - height) < 0.1 and + abs(edge_center[0] - 0.5*unit) % unit < 0.1 and + abs(edge_center[1] - 0.5*unit) % unit < 0.1)): + pass + else: + to_fillet.append(count) + outerbrick.fillet(fillet_rad, to_fillet) + + innerbrick = solidbrick(xsize, ysize, wall_thickness, unit, + height - wall_thickness, draft, + knob_rad - wall_thickness, knob_height, knob_draft) + base = ccad.box(2*unit*xsize, 2*unit*ysize, 1.0) + base.translate((-0.5*unit*xsize, -0.5*unit*ysize, -1.0)) + innerbrick = innerbrick + base + + if fillet: + to_fillet = [] + for count, edge_center in enumerate(innerbrick.subcenters('edge')): + if (abs(edge_center[2]) < 0.1 or + (abs(edge_center[2] - (height - wall_thickness)) < 0.1 and + abs(edge_center[0] - 0.5*unit) % unit < 0.1 and + abs(edge_center[1] - 0.5*unit) % unit < 0.1)): + to_fillet.append(count) + innerbrick.fillet(fillet_rad, to_fillet) + + brick = outerbrick - innerbrick + + post_rad = (math.sqrt(2.0)*unit - 2*knob_rad)/2.0 + drad = (height - 0.5*wall_thickness) * math.tan(math.radians(draft)) + post_base_rad = post_rad + drad + post = ccad.cone(post_rad, post_base_rad, height - 0.5*wall_thickness) + if fillet: + post.fillet(fillet_rad, [(0.0, 0.0, 0.0)]) + + post.translate((unit, unit, 0.0)) + for x in range(xsize - 1): + for y in range(ysize - 1): + brick = brick + ccad.translated(post, (x * unit, y * unit, 0.0)) + + return brick + diff --git a/src/contrib/ccad/doc/brick_bottom.png b/src/contrib/ccad/doc/brick_bottom.png new file mode 100644 index 000000000..156dce5e7 Binary files /dev/null and b/src/contrib/ccad/doc/brick_bottom.png differ diff --git a/src/contrib/ccad/doc/brick_images.py b/src/contrib/ccad/doc/brick_images.py new file mode 100644 index 000000000..fbbf83146 --- /dev/null +++ b/src/contrib/ccad/doc/brick_images.py @@ -0,0 +1,34 @@ +import math +import ccad +import brick + +def generate_images(xsize, ysize): + unit = 5.0 + height = 10.0 + center = (unit*xsize/2, unit*ysize/2, height/2) + projections = {'top': (center, (0.0, 0.0, 1.0), (0.0, 1.0, 0.0)), + 'side': (center, (1.0, 0.0, 0.0), (0.0, 0.0, 1.0)), + 'iso': (center, + (math.sqrt(0.4), -math.sqrt(0.4), math.sqrt(0.2)), + (-math.sqrt(0.5), math.sqrt(0.5), 0.0)), + 'bottom': (center, + (math.sqrt(0.4), -math.sqrt(0.4), -math.sqrt(0.2)), + (-math.sqrt(0.5), math.sqrt(0.5), 0.0))} + + b1 = brick.brick(xsize, ysize, fillet = False) + v1 = ccad.view() + + v1.set_background((1.0, 1.0, 1.0)) + v1.set_scale(24.0) + v1.set_size((600, 400)) + + for projection in projections: + vcenter, vout, vup = projections[projection] + v1.clear() + v1.set_projection(vcenter, vout, vup) + v1.display(b1, color = (0.0, 0.0, 0.0), line_width = 2.0) + v1.mode_drawing() + v1.save(name = 'brick_' + projection + '.png') + +if __name__ == '__main__': + generate_images(4, 2) diff --git a/src/contrib/ccad/doc/brick_iso.png b/src/contrib/ccad/doc/brick_iso.png new file mode 100644 index 000000000..7bc664e33 Binary files /dev/null and b/src/contrib/ccad/doc/brick_iso.png differ diff --git a/src/contrib/ccad/doc/brick_side.png b/src/contrib/ccad/doc/brick_side.png new file mode 100644 index 000000000..bae2fe78a Binary files /dev/null and b/src/contrib/ccad/doc/brick_side.png differ diff --git a/src/contrib/ccad/doc/brick_top.png b/src/contrib/ccad/doc/brick_top.png new file mode 100644 index 000000000..d000ed46d Binary files /dev/null and b/src/contrib/ccad/doc/brick_top.png differ diff --git a/src/contrib/ccad/doc/conf.py b/src/contrib/ccad/doc/conf.py new file mode 100644 index 000000000..b8f5f8d07 --- /dev/null +++ b/src/contrib/ccad/doc/conf.py @@ -0,0 +1,194 @@ +# -*- coding: utf-8 -*- +# +# ccad documentation build configuration file, created by +# sphinx-quickstart on Sat Mar 16 07:03:41 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.append(os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8' + +# The master toctree document. +master_doc = 'contents' + +# General information about the project. +project = u'ccad' +copyright = u'2014 by Charles Sharman' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '0.1' +# The full version, including alpha/beta/rc tags. +release = '0.1' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of documents that shouldn't be included in the build. +#unused_docs = [] + +# List of directories, relative to source directory, that shouldn't be searched +# for source files. +exclude_trees = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +html_theme = 'sphinxdoc' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_use_modindex = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = '' + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ccaddoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +# The paper size ('letter' or 'a4'). +#latex_paper_size = 'letter' + +# The font size ('10pt', '11pt' or '12pt'). +#latex_font_size = '10pt' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('contents', 'ccad.tex', u'ccad Documentation', + u'Charles Sharman', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# Additional stuff for the LaTeX preamble. +#latex_preamble = '' + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_use_modindex = True diff --git a/src/contrib/ccad/doc/configuring_display.rst b/src/contrib/ccad/doc/configuring_display.rst new file mode 100644 index 000000000..e9e67b15e --- /dev/null +++ b/src/contrib/ccad/doc/configuring_display.rst @@ -0,0 +1,31 @@ +Configuring Display +=================== + +The viewer has a few simple configuring options to customize it for +your application. + +add_menuitem +------------ + +You may add another pulldown menu item with **add_menuitem**. The +following example adds the item *Car1* under the *Model* menu:: + + v1.add_menuitem(('Model', 'Car1'), display_car, 1) + v1.add_menuitem(('Model', 'Car2'), display_car, 2) + +The first argument is a tuple that defines the menu hierarchy. The +first item in the tuple is the pull-down menu title. The last item is +the menu item. Intermediate items are submenus. If a menu doesn't +exist, it is created. **display_car** is the function to call when the +item is selected. Pass parameters follow the function call. +**display_car** might look like this:: + + def display_car(widget, version): + ... + +add_menu +-------- + +If you know gtk and you want menu items fancier than strings, +**add_menu**, a lower-level menu manipulation method is available. See +the API for details. diff --git a/src/contrib/ccad/doc/contents.rst b/src/contrib/ccad/doc/contents.rst new file mode 100644 index 000000000..3f0ab70d6 --- /dev/null +++ b/src/contrib/ccad/doc/contents.rst @@ -0,0 +1,11 @@ +Contents +======== + +.. toctree:: + :maxdepth: 2 + + intro + details + modelling + displaying + example1 diff --git a/src/contrib/ccad/doc/controlling_display.rst b/src/contrib/ccad/doc/controlling_display.rst new file mode 100644 index 000000000..331a62d9c --- /dev/null +++ b/src/contrib/ccad/doc/controlling_display.rst @@ -0,0 +1,117 @@ +Controlling Display +=================== + +The viewer has a number of methods that help control the display from +within your programs. + +viewstandard +------------ + +The viewing projection can be set to a standard projection with +viewstandard. The following example sets the view to front:: + + v1.viewstandard(viewtype = 'front') + +set_projection +-------------- + +The viewing projection can be set to any arbitrary projection with +set_projection. To look at the point (0.0, 0.0, 0.0) from the z-direction with the y-direction up, I could call:: + + v1.set_projection((0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, 1.0, 0.0)) + +set_scale +--------- + +set_scale scales the viewing window so the entire screen spans a +certain number of scene units. The following example sets the scale +such that a shape 8.0 long would span the screen:: + + v1.set_scale(8.0) + +set_size +-------- + +set_size sets the size of the viewing window in pixels. The following example sets the viewing window to be 600 by 800 pixels:: + + v1.set_size((600, 800)) + +set_background +-------------- + +set_background sets the background color. The following example sets +the background color to white:: + + v1.set_background((1.0, 1.0, 1.0)) + +set_foreground +-------------- + +set_foreground sets the default shape color. The following example +sets the foreground color to red:: + + v1.set_foreground((1.0, 0.0, 0.0)) + +set_triedron +------------ + +set_triedron sets how the triedron (the little x, y, z coordinate +sprite) is displayed. + +display +------- + +The examples have already used display extensively. However, there +are options to control color, material, transparency, line type, and +line width. The following example draws s1 red, in a metallized +material, halfway transparent:: + + s1 = ccad.sphere(1.0) + ccad.box(1.0, 2.0, 2.0) + v1.display(s1, color = (1.0, 0.0, 0.0), material = 'metallized', transparency = 0.5) + +.. image:: display_display.png + +fit +--- + +fits the scene to the window. + +clear +----- + +clear clears the window. + +save +---- + +save saves a screenshot. The following example saves the screen to +the file, 'screendump.png':: + + v1.save('screendump.png') + +mode_wireframe +-------------- + +mode_wireframe sets the window to wireframe display. + +mode_shaded +----------- + +mode_shaded sets the window to shaded display. + +mode_hlr +-------- + +mode_hlr sets the window to hidden line removal display. + +mode_drawing +------------ + +mode_drawing sets the window to a drafting-like drawing display by +using a custom hidden line removal mode. After calling it, call the +*reset_mode_drawing* method to restore regular control. + +quit +---- + +quit closes the window. diff --git a/src/contrib/ccad/doc/creating_derived.rst b/src/contrib/ccad/doc/creating_derived.rst new file mode 100644 index 000000000..880ccda16 --- /dev/null +++ b/src/contrib/ccad/doc/creating_derived.rst @@ -0,0 +1,135 @@ +Creating Derived Shapes +======================= + +Some shapes are derived from others and create arbitrary shapes, +depending on what's passed to them. They follow. + +prism +----- + +A prism extrudes a shape in a given linear direction. A vertex is +extruded as an edge; an edge is extruded as a face; a wire is extruded +as a shell; and a face is extruded as a solid. The following example +creates an extrusion of a hexagon:: + + f1 = ccad.plane(ccad.ngon(2.0, 6)) + s1 = ccad.prism(f1, (0.0, 0.0, 1.0)) + +.. image:: derived_prism.png + +revol +----- + +A revol extrudes a shape in a circle. A vertex is extruded as an +edge; an edge is extruded as a face; a wire is extruded as a shell; +and a face is extruded as a solid. The following example extrudes a +squared circle about (0.0, 0.0, 0.0):: + + e1 = ccad.arc(1.0, -math.pi/2, math.pi/2) + e1.translate((3.0, 0.0, 0.0)) + w1 = ccad.polygon([(3.0, 1.0, 0.0), + (2.0, 1.0, 0.0), + (2.0, -1.0, 0.0), + (3.0, -1.0, 0.0)]) + f1 = ccad.plane(ccad.wire([e1, w1])) + s1 = ccad.revol(f1, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), 2*math.pi) + +.. image:: derived_revol.png + +loft +---- + +A loft returns a solid or shell that fits a list of closed wires. The +following example fits a changing-diameter circle:: + + w1 = ccad.wire([ccad.circle(1.0)]) + w2 = ccad.wire([ccad.circle(2.0)]) + w2.translate((0.0, 0.0, 1.0)) + w3 = ccad.wire([ccad.circle(1.5)]) + w3.translate((0.0, 0.0, 2.0)) + s1 = ccad.loft([w1, w2, w3], stype = 'solid') + +.. image:: derived_loft.png + +pipe +---- + +A pipe returns a solid or shell that is an extrusion of a closed wire +profile along a wire spine. The following example extrudes a hexagon +along an arching and then straight spine:: + + profile = ccad.ngon(2.0, 6) + e1 = ccad.arc(8.0, 0.0, math.pi/2) + e2 = ccad.segment((0.0, 8.0, 0.0), (-8.0, 8.0, 0.0)) + spine = ccad.wire([e1, e2]) + spine.translate((-8.0, 0.0, 0.0)) + spine.rotatex(math.pi/2) + s1 = ccad.pipe(profile, spine) + +.. image:: derived_pipe.png + +helical_solid +------------- + +A helical_solid is a pipe spun on a helical spine. Unfortunately, +pipe had trouble with the profile's orientation on a helix, so +helical_solid was needed. The following example extrudes a triangle +along a helix:: + + profile = ccad.ngon(0.2, 3) + s1 = ccad.helical_solid(profile, 2.0, 1.0/math.pi, 2) + +.. image:: derived_helical_solid.png + +offset +------ + +offset offsets a face or solid by a given distance and returns a list +of faces or solids. Why doesn't it return a single face or solid? +Depending on the distance, offsetting can create multiple shapes from +one. For example, consider a 2D version of an octopus. As offset +gets larger and larger, parts of the face merge and can create holes. +The following example offsets a hexagon by 1.0. In this example, the +returned list is only one item long:: + + w1 = ccad.ngon(8.0, 6) + w2 = ccad.offset(ccad.plane(w1), 1.0)[0] + +.. image:: derived_offset_face.png + +The following example offsets a box with a hole by 1.0. In this +example, the returned list is only one item long:: + + b1 = ccad.box(10.0, 10.0, 10.0) + b1.translate((-5.0, -5.0, 0.0)) + c1 = ccad.cylinder(2.5, 20.0) + c1.translate((0.0, 0.0, -5.0)) + s1 = b1 - c1 + s2 = ccad.offset(s1, 1.0)[0] + +.. image:: derived_offset_solid.png + +Note the offset shape has edges rounded. Currently, this is the only +option OCC provides. + +A positive value for offset makes the new face or solid outside the +original. A negative value makes the new face or solid inside the +original. + +slice +----- + +slice slices a solid with a plane and returns a list of faces at the +intersection of the solid and plane. The plane can be passed or a +value for x, y, or z can be given which generates the plane. The +following example slices a box with a hole at z = 1.0:: + + b1 = ccad.box(10.0, 10.0, 10.0) + b1.translate((-5.0, -5.0, 0.0)) + c1 = ccad.cylinder(2.5, 20.0) + c1.translate((0.0, 0.0, -5.0)) + s1 = b1 - c1 + f1 = ccad.slice(s1, z = 1.0)[0] + +.. image:: derived_slice.png + diff --git a/src/contrib/ccad/doc/creating_edges.rst b/src/contrib/ccad/doc/creating_edges.rst new file mode 100644 index 000000000..780a1c994 --- /dev/null +++ b/src/contrib/ccad/doc/creating_edges.rst @@ -0,0 +1,90 @@ +Creating Edges +============== + +Functions that instantiate an edge follow. In each example, the edge +is called **e1**. + +segment +------- + +A segment defines a straight line from one point to another. The +following example creates a segment from the origin to the point +(1.0, 0.0, 0.0):: + + pt1 = (0.0, 0.0, 0.0) + pt2 = (1.0, 0.0, 0.0) + e1 = ccad.segment(pt1, pt2) + +.. image:: edge_segment.png + +arc +--- + +An arc defines a portion of a circle. The following example creates +an arc centered on the origin of radius 1.0 that extends from 0.0 +radians to pi/2 radians:: + + e1 = ccad.arc(1.0, 0.0, math.pi/2) + +.. image:: edge_arc.png + +arc_ellipse +----------- + +An arc_ellipse defines a portion of an ellipse. The following example +creates an arc_ellipse centered on the origin with major radius 2.0, +minor radius 1.0, and that extends from 0.0 radians to pi/2 radians:: + + e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2) + +.. image:: edge_arc_ellipse.png + +spline +------ + +A spline defines a 3D spline fit to a series of passed points. The +following example creates a spline that passes through the points +(0.0, 0.0, 0.0), (0.2, 0.1, 0.0), (0.5, 0.2, 0.0), (-0.5, 0.3, 0.0):: + + e1 = ccad.spline([(0.0, 0.0, 0.0), + (0.2, 0.1, 0.0), + (0.5, 0.2, 0.0), + (-0.5, 0.3, 0.0)]) + +.. image:: edge_spline.png + +bezier +------ + +A bezier edge is an edge that passes from and to the endpoints. The +intermediate points shape the curve. The following example creates a +bezier that is the same as a quarter circle:: + + e1 = ccad.bezier([(1.0, 0.0, 0.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 0.0)], [1.0, 1.0/math.sqrt(2.0), 1.0]) + +.. image:: edge_bezier.png + +It can also be called without weights, which will return the curves +used in svg files. + +circle +------ + +The following example creates a circle centered on the origin with +radius 3.0:: + + e1 = ccad.circle(3.0) + +.. image:: edge_circle.png + +ellipse +------- + +The following example creates an ellipse centered on the origin with major radius 2.0 and minor radius 1.0:: + + e1 = ccad.ellipse(2.0, 1.0) + +.. image:: edge_ellipse.png + diff --git a/src/contrib/ccad/doc/creating_faces.rst b/src/contrib/ccad/doc/creating_faces.rst new file mode 100644 index 000000000..0b2428e12 --- /dev/null +++ b/src/contrib/ccad/doc/creating_faces.rst @@ -0,0 +1,52 @@ +Creating Faces +============== + +Functions that instantiate a face follow. In each example, the face +is called **f1**. + +plane +----- + +A plane is a flat shape bounded by a closed wire in 3D space. The +wire must be planar (flat) to work. The following example creates a +face inside an ngon:: + + w1 = ccad.ngon(2.0, 5) + f1 = ccad.plane(w1) + +.. image:: face_plane.png + +face_from +--------- + +face_from creates a face that mathematically exactly matches another +face but is bounded by a different wire. The following example +creates a filled square from a filled octagon. The example isn't very +practical. face_from is more often used when the face is 3D and you +want to bound it by a different wire:: + + w1 = ccad.ngon(2.0, 8) + w2 = ccad.ngon(10.0, 4) + f2 = ccad.plane(w1) + f1 = ccad.face_from(f2, w2) + +.. image:: face_face_from.png + +filling +------- + +A filling is a surface fit to a wire, and that wire does not need to +be planar. The following example fits a surface to a wavy wire. Both +the wire and face are shown:: + + w1 = ccad.spline([(0.0, 0.0, 0.0), + (1.0, 0.2, 0.3), + (1.5, 0.8, 1.0), + (0.8, 1.2, 0.2), + (-0.5, 0.8, -0.5), + (0.0, 0.0, 0.0)]) + f1 = ccad.filling(w1) + +.. image:: face_filling.png + +Visit the API documentation for more filling options. diff --git a/src/contrib/ccad/doc/creating_solids.rst b/src/contrib/ccad/doc/creating_solids.rst new file mode 100644 index 000000000..e86edea3d --- /dev/null +++ b/src/contrib/ccad/doc/creating_solids.rst @@ -0,0 +1,75 @@ +Creating Solids +=============== + +Functions that instantiate a solid follow. In each example, the solid +is called **s1**. + +box +--- + +A box is a six-sided perpendicular-faced solid. The following example +creates a box that extends 1.0 in the x-direction, 2.0 in the +y-direction, and 3.0 in the z-direction:: + + s1 = ccad.box(1.0, 2.0, 3.0) + +.. image:: solid_box.png + +wedge +----- + +A wedge is a box with one or more tapered sides. The following +example creates a wedge that extends 2.0 in the y-direction and 3.0 in +the z-direction and tapers from 1.0 to 0.5 in the x-direction:: + + s1 = ccad.wedge(1.0, 2.0, 3.0, 0.5) + +.. image:: solid_wedge.png + +cylinder +-------- + +The following example creates a cylinder 1.0 in radius and 2.0 high:: + + s1 = ccad.cylinder(1.0, 2.0) + +.. image:: solid_cylinder.png + +sphere +------ + +The following example creates a sphere 5.0 in radius:: + + s1 = ccad.sphere(5.0) + +.. image:: solid_sphere.png + +cone +---- + +The following example creates the bottom of a cone, radius 4.0 at the +bottom, 2.0 at the top, and 2.0 high:: + + s1 = ccad.cone(4.0, 2.0, 2.0) + +.. image:: solid_cone.png + +A similar function, bezier_cone, returns an identical solid, but the +cone face is a surface of revolution. Fillets worked better with it +sometimes. + +torus +----- + +The following example creates a torus 10.0 from torus center to +extruded circle center, and 1.0 in radius:: + + s1 = ccad.torus(10.0, 1.0) + +.. image:: solid_torus.png + +Notes +----- + +Many of these functions offer more options than shown. Consult the +API for details. diff --git a/src/contrib/ccad/doc/creating_wires.rst b/src/contrib/ccad/doc/creating_wires.rst new file mode 100644 index 000000000..0cc768096 --- /dev/null +++ b/src/contrib/ccad/doc/creating_wires.rst @@ -0,0 +1,71 @@ +Creating Wires +============== + +Functions that instantiate a wire follow. In each example, the wire +is called **w1**. + +Connection of Edges +------------------- + +wire is one of the few classes you'll instantiate directly in ccad. +Often, you'll create a wire manually by connecting multiple edges. Do +it by passing a list of edges to a wire instantiation. The following +example creates an elongated circle:: + + e1 = ccad.arc(1.0, -math.pi/2, math.pi/2) + e1.translate((1.0, 0.0, 0.0)) + e2 = ccad.segment((1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)) + e3 = ccad.arc(1.0, math.pi/2, 3*math.pi/2) + e3.translate((-1.0, 0.0, 0.0)) + e4 = ccad.segment((-1.0, -1.0, 0.0), (1.0, -1.0, 0.0)) + w1 = cm.wire([e1, e2, e3, e4]) + +.. image:: wire.png + +You'll learn more about *translate* later. + +polygon +------- + +A polygon defines a connection of segments. To close the polygon, +make the last point equal to the first. The following example creates +a polygon:: + + w1 = ccad.polygon([(0.0, 0.0, 0.0), + (1.0, 0.0, 0.0), + (1.5, 1.0, 0.0), + (0.5, 1.5, 0.0), + (-0.5, -0.5, 0.0)]) + +.. image:: wire_polygon.png + +rectangle +--------- + +Create a rectangle by passing the x-distance and y-distance. The following example creates a rectangle 2.0 wide and 1.0 high. The lower-left corner is at (0.0, 0.0, 0.0):: + + w1 = ccad.rectangle(2.0, 1.0) + +.. image:: wire_rectangle.png + +ngon +---- + +An ngon defines an equal-sided polygon with n segments. The following +example creates a hexagon with radius (distance from center to vertex) +of 2.0:: + + w1 = ccad.ngon(2.0, 6) + +.. image:: wire_ngon.png + +helix +----- + +The following example creates a helix with radius 2.0, helical angle +of 1.0/pi, and 3 turns:: + + w1 = ccad.helix(2.0, 1.0/math.pi, 3) + +.. image:: wire_helix.png + diff --git a/src/contrib/ccad/doc/cube_edge.png b/src/contrib/ccad/doc/cube_edge.png new file mode 100644 index 000000000..478023726 Binary files /dev/null and b/src/contrib/ccad/doc/cube_edge.png differ diff --git a/src/contrib/ccad/doc/cube_face.png b/src/contrib/ccad/doc/cube_face.png new file mode 100644 index 000000000..c78af4604 Binary files /dev/null and b/src/contrib/ccad/doc/cube_face.png differ diff --git a/src/contrib/ccad/doc/cube_solid.png b/src/contrib/ccad/doc/cube_solid.png new file mode 100644 index 000000000..3786ad086 Binary files /dev/null and b/src/contrib/ccad/doc/cube_solid.png differ diff --git a/src/contrib/ccad/doc/cube_vertex.png b/src/contrib/ccad/doc/cube_vertex.png new file mode 100644 index 000000000..4610014e3 Binary files /dev/null and b/src/contrib/ccad/doc/cube_vertex.png differ diff --git a/src/contrib/ccad/doc/cube_wire.png b/src/contrib/ccad/doc/cube_wire.png new file mode 100644 index 000000000..5365c37ba Binary files /dev/null and b/src/contrib/ccad/doc/cube_wire.png differ diff --git a/src/contrib/ccad/doc/derived_helical_solid.png b/src/contrib/ccad/doc/derived_helical_solid.png new file mode 100644 index 000000000..ca5f332e0 Binary files /dev/null and b/src/contrib/ccad/doc/derived_helical_solid.png differ diff --git a/src/contrib/ccad/doc/derived_loft.png b/src/contrib/ccad/doc/derived_loft.png new file mode 100644 index 000000000..72675bdb5 Binary files /dev/null and b/src/contrib/ccad/doc/derived_loft.png differ diff --git a/src/contrib/ccad/doc/derived_offset_face.png b/src/contrib/ccad/doc/derived_offset_face.png new file mode 100644 index 000000000..f8263a780 Binary files /dev/null and b/src/contrib/ccad/doc/derived_offset_face.png differ diff --git a/src/contrib/ccad/doc/derived_offset_solid.png b/src/contrib/ccad/doc/derived_offset_solid.png new file mode 100644 index 000000000..e6189a2b1 Binary files /dev/null and b/src/contrib/ccad/doc/derived_offset_solid.png differ diff --git a/src/contrib/ccad/doc/derived_pipe.png b/src/contrib/ccad/doc/derived_pipe.png new file mode 100644 index 000000000..e8ef5e994 Binary files /dev/null and b/src/contrib/ccad/doc/derived_pipe.png differ diff --git a/src/contrib/ccad/doc/derived_prism.png b/src/contrib/ccad/doc/derived_prism.png new file mode 100644 index 000000000..f33306d97 Binary files /dev/null and b/src/contrib/ccad/doc/derived_prism.png differ diff --git a/src/contrib/ccad/doc/derived_revol.png b/src/contrib/ccad/doc/derived_revol.png new file mode 100644 index 000000000..608fa448d Binary files /dev/null and b/src/contrib/ccad/doc/derived_revol.png differ diff --git a/src/contrib/ccad/doc/derived_slice.png b/src/contrib/ccad/doc/derived_slice.png new file mode 100644 index 000000000..207478996 Binary files /dev/null and b/src/contrib/ccad/doc/derived_slice.png differ diff --git a/src/contrib/ccad/doc/details.rst b/src/contrib/ccad/doc/details.rst new file mode 100644 index 000000000..d2fb21f41 --- /dev/null +++ b/src/contrib/ccad/doc/details.rst @@ -0,0 +1,58 @@ +Details +======= + +Internals +--------- + +ccad is a python wrapper of pythonocc, which is a python wrapper of +OpenCascade, an open source mechanical CAD math engine. So why wrap +python with more python? pythonocc is a SWIG wrapper of a C++ +project. Because of that, the syntax is fairly cumbersome. The +sphere instance from the introduction page looks like this in +pythonocc:: + + from OCC.BRepPrimAPI import * + s1 = BRepPrimAPI_Sphere(10.0).Shape() + +ccad syntax is simpler, and allows you to focus more on design and +less on syntax. Put simply, pythonocc is more for CAD developers; +ccad is more for CAD users. + +Unfortunately, ccad use comes with a cost. Not all of pythonocc's +abilities are yet wrapped. Therefore, it lacks the power of +pythonocc. + +However, extending ccad isn't too hard. You can extend ccad with +python calls to pythoncc. No C or C++ coding is necessary. We're +always looking for pythonocc-skilled people to extend ccad's +abilities. + +User Requirements +----------------- + +You'll need to know python reasonably well to use ccad. Additionally, +the *pydoc* generated documentation adds further detail to this +manual. Keep it handly. + +System Requirements +------------------- + +You'll need pythonocc, python-gtk, and python-gtkglext to run ccad. + +Installation +------------ + +To install ccad, follow the following procedure in Linux:: + + tar xvzf ccad-ver.tar.gz (where ver is the version number) + cd ccad-ver (where ver is the version number) + python setup.py install --prefix=/usr/local (as root) + +Change the prefix argument to install in a different directory. + +If you're a Windows or Mac user, ccad should still work. I just +haven't tried it. If you successfully install ccad in Windows or Mac, +let us know how you did it, and we'll update the documentation. + +If you're having trouble, simply extract the .tar.gz file. Then, add +that directory to your PYTHONPATH. That, at least, will get you going. diff --git a/src/contrib/ccad/doc/display_display.png b/src/contrib/ccad/doc/display_display.png new file mode 100644 index 000000000..679a30144 Binary files /dev/null and b/src/contrib/ccad/doc/display_display.png differ diff --git a/src/contrib/ccad/doc/display_hlr.png b/src/contrib/ccad/doc/display_hlr.png new file mode 100644 index 000000000..032ee223c Binary files /dev/null and b/src/contrib/ccad/doc/display_hlr.png differ diff --git a/src/contrib/ccad/doc/display_shaded.png b/src/contrib/ccad/doc/display_shaded.png new file mode 100644 index 000000000..940fbbb5b Binary files /dev/null and b/src/contrib/ccad/doc/display_shaded.png differ diff --git a/src/contrib/ccad/doc/display_spherebox1.png b/src/contrib/ccad/doc/display_spherebox1.png new file mode 100644 index 000000000..647b66daa Binary files /dev/null and b/src/contrib/ccad/doc/display_spherebox1.png differ diff --git a/src/contrib/ccad/doc/display_spherebox1.py b/src/contrib/ccad/doc/display_spherebox1.py new file mode 100644 index 000000000..e0d1214a9 --- /dev/null +++ b/src/contrib/ccad/doc/display_spherebox1.py @@ -0,0 +1,13 @@ +import ccad + +def main(): + s1 = ccad.sphere(1.0) + s2 = ccad.box(1.0, 2.0, 3.0) + s2.translate((2.0, 0.0, 0.0)) + v1 = ccad.view() + v1.display(s1) + v1.display(s2) + ccad.start() + +if __name__ == '__main__': + main() diff --git a/src/contrib/ccad/doc/display_wireframe.png b/src/contrib/ccad/doc/display_wireframe.png new file mode 100644 index 000000000..c1e887999 Binary files /dev/null and b/src/contrib/ccad/doc/display_wireframe.png differ diff --git a/src/contrib/ccad/doc/displaying.rst b/src/contrib/ccad/doc/displaying.rst new file mode 100644 index 000000000..7563df919 --- /dev/null +++ b/src/contrib/ccad/doc/displaying.rst @@ -0,0 +1,15 @@ +Displaying +========== + +You have your model in text. Now you want to see it. The ccad viewer +can be run stand-alone or interactively. + +.. toctree:: + :maxdepth: 2 + + stand_alone + interactive + using_display + controlling_display + configuring_display + diff --git a/src/contrib/ccad/doc/edge_arc.png b/src/contrib/ccad/doc/edge_arc.png new file mode 100644 index 000000000..312bb0c1d Binary files /dev/null and b/src/contrib/ccad/doc/edge_arc.png differ diff --git a/src/contrib/ccad/doc/edge_arc_ellipse.png b/src/contrib/ccad/doc/edge_arc_ellipse.png new file mode 100644 index 000000000..f6ccaffcb Binary files /dev/null and b/src/contrib/ccad/doc/edge_arc_ellipse.png differ diff --git a/src/contrib/ccad/doc/edge_bezier.png b/src/contrib/ccad/doc/edge_bezier.png new file mode 100644 index 000000000..bd4ba4b59 Binary files /dev/null and b/src/contrib/ccad/doc/edge_bezier.png differ diff --git a/src/contrib/ccad/doc/edge_circle.png b/src/contrib/ccad/doc/edge_circle.png new file mode 100644 index 000000000..7998ce3f6 Binary files /dev/null and b/src/contrib/ccad/doc/edge_circle.png differ diff --git a/src/contrib/ccad/doc/edge_ellipse.png b/src/contrib/ccad/doc/edge_ellipse.png new file mode 100644 index 000000000..1c0a4d523 Binary files /dev/null and b/src/contrib/ccad/doc/edge_ellipse.png differ diff --git a/src/contrib/ccad/doc/edge_segment.png b/src/contrib/ccad/doc/edge_segment.png new file mode 100644 index 000000000..2be996198 Binary files /dev/null and b/src/contrib/ccad/doc/edge_segment.png differ diff --git a/src/contrib/ccad/doc/edge_spline.png b/src/contrib/ccad/doc/edge_spline.png new file mode 100644 index 000000000..1da897465 Binary files /dev/null and b/src/contrib/ccad/doc/edge_spline.png differ diff --git a/src/contrib/ccad/doc/example1.rst b/src/contrib/ccad/doc/example1.rst new file mode 100644 index 000000000..8c6083204 --- /dev/null +++ b/src/contrib/ccad/doc/example1.rst @@ -0,0 +1,30 @@ +Example - Building Toy +====================== + +Suppose I want to generate a brick building toy for plastic injection +molding, similar to the one we know and love. I'm looking for bricks +with the following unit configurations all the same height. + +- 1 x 1 +- 2 x 1 +- 4 x 1 +- 6 x 1 +- 8 x 1 +- 2 x 2 +- 4 x 2 +- 8 x 2 +- 4 x 4 + +10mm is the brick height, and a unit brick will be 5mm by 5mm in x and +y. I'll use ipython to develop and modify. When I'm pleased with a +block of code, I'll place it in a file for future use. I won't show +the ipython prompt. + +.. toctree:: + :maxdepth: 2 + + example1_singlebrick + example1_brickpy + example1_multibrick + example1_drawings + example1_nuances diff --git a/src/contrib/ccad/doc/example1_all_bricks.png b/src/contrib/ccad/doc/example1_all_bricks.png new file mode 100644 index 000000000..b2baee82b Binary files /dev/null and b/src/contrib/ccad/doc/example1_all_bricks.png differ diff --git a/src/contrib/ccad/doc/example1_box.png b/src/contrib/ccad/doc/example1_box.png new file mode 100644 index 000000000..800b142d6 Binary files /dev/null and b/src/contrib/ccad/doc/example1_box.png differ diff --git a/src/contrib/ccad/doc/example1_boxoffset.png b/src/contrib/ccad/doc/example1_boxoffset.png new file mode 100644 index 000000000..50d486a6c Binary files /dev/null and b/src/contrib/ccad/doc/example1_boxoffset.png differ diff --git a/src/contrib/ccad/doc/example1_boxwfillets.png b/src/contrib/ccad/doc/example1_boxwfillets.png new file mode 100644 index 000000000..267056ce4 Binary files /dev/null and b/src/contrib/ccad/doc/example1_boxwfillets.png differ diff --git a/src/contrib/ccad/doc/example1_boxwknob.png b/src/contrib/ccad/doc/example1_boxwknob.png new file mode 100644 index 000000000..713ad1bb2 Binary files /dev/null and b/src/contrib/ccad/doc/example1_boxwknob.png differ diff --git a/src/contrib/ccad/doc/example1_boxwknobs.png b/src/contrib/ccad/doc/example1_boxwknobs.png new file mode 100644 index 000000000..766b7480c Binary files /dev/null and b/src/contrib/ccad/doc/example1_boxwknobs.png differ diff --git a/src/contrib/ccad/doc/example1_brick.png b/src/contrib/ccad/doc/example1_brick.png new file mode 100644 index 000000000..b17505efb Binary files /dev/null and b/src/contrib/ccad/doc/example1_brick.png differ diff --git a/src/contrib/ccad/doc/example1_brickpost.png b/src/contrib/ccad/doc/example1_brickpost.png new file mode 100644 index 000000000..65136ab52 Binary files /dev/null and b/src/contrib/ccad/doc/example1_brickpost.png differ diff --git a/src/contrib/ccad/doc/example1_brickpy.rst b/src/contrib/ccad/doc/example1_brickpy.rst new file mode 100644 index 000000000..875d68d48 --- /dev/null +++ b/src/contrib/ccad/doc/example1_brickpy.rst @@ -0,0 +1,10 @@ +brick.py +======== + +Now that I'm satisfied with my brick, I'll place the ipython code in a +function in **brick.py**. I'll call the function **brick** and pass **xsize** +and **ysize** as parameters. I'll also make the fillets an option. +**brick.py** now looks like this: + +.. include:: brick.py + :literal: diff --git a/src/contrib/ccad/doc/example1_core.png b/src/contrib/ccad/doc/example1_core.png new file mode 100644 index 000000000..2a3abe7e4 Binary files /dev/null and b/src/contrib/ccad/doc/example1_core.png differ diff --git a/src/contrib/ccad/doc/example1_drawings.rst b/src/contrib/ccad/doc/example1_drawings.rst new file mode 100644 index 000000000..7ffb004e6 --- /dev/null +++ b/src/contrib/ccad/doc/example1_drawings.rst @@ -0,0 +1,90 @@ +Drawings +======== + +OCC has a number of capabilities to turn a model into engineering +drawings with dimensions and annotations. ccad does not yet implement +them, but the hidden line removal features are enough to generate some +simple patent drawings. + +Requirements +------------ + +I'm pleased with my brick, and I'd like to create some patent drawings +from it. I'd like top, side, isometric, and bottom views. In case I +change my brick, I'd like the drawings to auto-generate. I'll use the +*4x2* example. + +brick_images.py +--------------- + +I'll create a file called **brick_images.py**. It starts like this:: + + import ccad + import brick + + def generate_images(xsize, ysize): + pass + + if __name__ == '__main__': + generate_images() + +Currently, it does nothing. I want to write the **generate_images** +function. First, I'll create the projection vectors for the various +views:: + + unit = 5.0 + height = 10.0 + center = (unit*xsize/2, unit*ysize/2, height/2) + projections = {'top': (center, (0.0, 0.0, 1.0), (0.0, 1.0, 0.0)), + 'side': (center, (1.0, 0.0, 0.0), (0.0, 0.0, 1.0)), + 'iso': (center, + (math.sqrt(0.4), -math.sqrt(0.4), math.sqrt(0.2)), + (-math.sqrt(0.5), math.sqrt(0.5), 0.0)), + 'bottom': (center, + (math.sqrt(0.4), -math.sqrt(0.4), -math.sqrt(0.2)), + (-math.sqrt(0.5), math.sqrt(0.5), 0.0))} + +Now, I'll create a brick and view instance:: + + b1 = brick.brick(xsize, ysize, fillet = False) + v1 = ccad.view() + +Note that I called brick with fillet set to *False*. Smooth edges +(e.g. filleted ones) are not shown in *drawing* mode. We need them +sharp to see them. + +Now, I'll fix the background, scale, and size of the view:: + + v1.set_background((1.0, 1.0, 1.0)) + v1.set_scale(24.0) + v1.set_size((600, 400)) + +Finally, I'll iterate over each projection, display it in black lines, +and save it:: + + for projection in projections: + vcenter, vout, vup = projections[projection] + v1.clear() + v1.set_projection(vcenter, vout, vup) + v1.display(b1, color = (0.0, 0.0, 0.0), line_width = 2.0) + v1.mode_drawing() + v1.save(name = 'brick_' + projection + '.png') + +Note the **mode_drawing** call after **display**. This redraws the image +in a drafting-like mode. + +Now, I'll call **generate_images** on start:: + + if __name__ == '__main__': + generate_images(4, 2) + +When I run the program, it generates the following images: + +.. image:: brick_top.png + +.. image:: brick_side.png + +.. image:: brick_iso.png + +.. image:: brick_bottom.png + diff --git a/src/contrib/ccad/doc/example1_innerbrick.png b/src/contrib/ccad/doc/example1_innerbrick.png new file mode 100644 index 000000000..00c78c697 Binary files /dev/null and b/src/contrib/ccad/doc/example1_innerbrick.png differ diff --git a/src/contrib/ccad/doc/example1_innerbrickfillet.png b/src/contrib/ccad/doc/example1_innerbrickfillet.png new file mode 100644 index 000000000..b6a885154 Binary files /dev/null and b/src/contrib/ccad/doc/example1_innerbrickfillet.png differ diff --git a/src/contrib/ccad/doc/example1_multibrick.rst b/src/contrib/ccad/doc/example1_multibrick.rst new file mode 100644 index 000000000..9e3d0be40 --- /dev/null +++ b/src/contrib/ccad/doc/example1_multibrick.rst @@ -0,0 +1,50 @@ +Multiple Bricks +=============== + +Because ccad is python-programming, it allows huge power in design +reuse. Our building toy serves as a great example. + +I'll create a new file and call it **all_bricks.py**. First, I'll +define the various brick sizes I want in the toy:: + + import ccad + import brick + + sizes = [(1, 1), + (2, 1), + (4, 1), + (6, 1), + (8, 1), + (2, 2), + (2, 4), + (2, 8), + (4, 4)] + +Now, I'll define a display function to view a given brick:: + + def display_brick(widget, view, size): + view.clear() + view.display(brick.brick(size[0], size[1])) + +Finally, I'll write the code to run at startup:: + + if __name__ == '__main__': + v1 = ccad.view() + for size in sizes: + name = 'brick' + str(size[0]) + 'x' + str(size[1]) + v1.add_menuitem(('Bricks', name), display_brick, v1, size) + ccad.start() + +The startup code instantiates a view, adds a menu item for every brick +in **sizes** and starts the viewer in stand-alone mode. + +The figure below shows the **all_bricks.py** window with the *8x1* brick +displayed: + +.. image:: example1_all_bricks.png + +Try to appreciate the power. With a few more lines, I can generate 9 +different bricks from single brick code, and because it's python, the +complexity is nearly unlimited. By contrast, complicated model reuse +can be difficult-to-impossible in GUI-based CAD systems. + diff --git a/src/contrib/ccad/doc/example1_nuances.rst b/src/contrib/ccad/doc/example1_nuances.rst new file mode 100644 index 000000000..36300f2aa --- /dev/null +++ b/src/contrib/ccad/doc/example1_nuances.rst @@ -0,0 +1,9 @@ +Nuances +======= + +While the building toy example appears to be a step-by-step smooth +process, in reality, it wasn't. OCC often has trouble with fillets +and boolean operations. I had to play with the model and fillets +before I had something which worked robustly. I originally included +those trails-to-nowhere to help you see how they can be overcome, but +it became over-complicated. I removed them, and it looks simple now. diff --git a/src/contrib/ccad/doc/example1_outerbrick.png b/src/contrib/ccad/doc/example1_outerbrick.png new file mode 100644 index 000000000..9ffa4cfc0 Binary files /dev/null and b/src/contrib/ccad/doc/example1_outerbrick.png differ diff --git a/src/contrib/ccad/doc/example1_post.png b/src/contrib/ccad/doc/example1_post.png new file mode 100644 index 000000000..01a0260d4 Binary files /dev/null and b/src/contrib/ccad/doc/example1_post.png differ diff --git a/src/contrib/ccad/doc/example1_singlebrick.rst b/src/contrib/ccad/doc/example1_singlebrick.rst new file mode 100644 index 000000000..7e36fe5a0 --- /dev/null +++ b/src/contrib/ccad/doc/example1_singlebrick.rst @@ -0,0 +1,228 @@ +A Single Brick +-------------- + +First, I'll start by importing the required packages:: + + import math + import ccad + +Now, I'll define some dimensions for all bricks:: + + unit = 5.0 + height = 10.0 + knob_rad = 1.8 # radius of the brick knob for mating with other bricks + knob_draft = 5.0 # degrees of draft for the knob + knob_height = 2.0 + wall_thickness = 1.6 # plastic wall thickness + draft = 1.0 # degrees of draft on faces for plastic ejection + fillet_rad = 0.4 # the default radius to use for rounded edges + +In ipython, I set up the specific brick's dimensions. Later, these +will be pass parameters in a function:: + + xsize = 4 + ysize = 2 + +Now, I begin making the outer shape. I could use **box**, but most +injection molders want you to draft the walls to make machining and +ejection easier. **loft* tends to be more robust than **draft** on +arbitrary shapes, so I'll use that:: + + dx = height * math.tan(math.radians(draft)) + wbottom = ccad.rectangle(unit * xsize, unit * ysize) + wtop = ccad.rectangle(unit * xsize - 2*dx, unit * ysize - 2*dx) + wtop.translate((dx, dx, height)) + brick = ccad.loft([wbottom, wtop], True) + + view1 = ccad.view() + view1.display(brick) + +.. image:: example1_box.png + +Here, the brick bottom is defined by the wire, **wbottom**, and the +brick top is defined by the wire, **wtop**. The **loft** with ruled set +to *True* converts the wires to a ruled solid: + +I need a knob on top for mating with bricks above, and I want that +knob to aid in alignment by tapering it. A cone would do nicely:: + + drad = knob_height * math.tan(math.radians(knob_draft)) + knob_top_rad = knob_rad - drad + knob_base_rad = knob_rad + drad + knob = ccad.cone(knob_base_rad, knob_top_rad, 2 * knob_height) + knob.translate((0.5 * unit, 0.5 * unit, height - knob_height)) + +Notice the knob is higher than it needed to be, then offset. Usually, +boolean operations are less buggy when surfaces don't coincide on +their faces. That is, make the knob penetrate into the brick for more +robust boolean operations. + +Now, I add the knob to the view to make sure I like the position. +It's added in red:: + + view1.display(knob, color = (1.0, 0.0, 0.0)) + +.. image:: example1_boxwknob.png + +Now, I'll replicate the knob and fuse it with the brick:: + + for x in range(xsize): + for y in range(ysize): + brick = brick + ccad.translated(knob, (x * unit, y * unit, 0.0)) + + view1.clear() + view1.display(brick) + +.. image:: example1_boxwknobs.png + +Note the use of the *function* form of translate here: +**ccad.translated** versus the *method* form for wtop: **wtop.translate**. +I use the function form when I want to *copy* a shape. I use the +method form when I want to *change* a shape. + +Injection molders like same-thickness walls, so I need to core-out the +brick. At this point, it looks like most of my code can be replicated +for the core, so I'll place the solid brick in its own function and +add a **wall_offset** to its pass parameters and place it in a file +called **brick.py**:: + + import ccad + import math + + def solidbrick(xsize, ysize, wall_offset, unit, height, draft, + knob_rad, knob_height, knob_draft): + + dx = height * math.tan(math.radians(draft)) + wbottom = ccad.rectangle(unit*xsize - 2*wall_offset, + unit*ysize - 2*wall_offset) + wtop = ccad.rectangle(unit*xsize - 2*dx - 2*wall_offset, + unit*ysize - 2*dx - 2*wall_offset) + wtop.translate((dx, dx, height)) + brick = ccad.loft([wbottom, wtop], True) + brick.translate((wall_offset, wall_offset, 0.0)) + + drad = knob_height * math.tan(math.radians(knob_draft)) + knob_top_rad = knob_rad - drad + knob_base_rad = knob_rad + drad + knob = ccad.cone(knob_base_rad, knob_top_rad, 2 * knob_height) + knob.translate((0.5 * unit, 0.5 * unit, height - knob_height)) + + for x in range(xsize): + for y in range(ysize): + brick = brick + ccad.translated(knob, (x * unit, y * unit, 0.0)) + + return brick + +And, I can import it in ipython:: + + import brick + + outerbrick = brick.solidbrick(xsize, ysize, 0.0, unit, height, draft, + knob_rad, knob_height, knob_draft) + +**outerbrick** is now the outside of the brick. + +Before I create the inside of the brick, I ought to add fillets to +**outerbrick**. Many injection molders use milling to form the mold. +Milling requires fillets wherever the ball endmill can't make a sharp +corner. For the cavity, it's the convex edges. For the core, it's +the concave edges. To make things simple, I usually fillet every +edge. However, to show off some of ccad's features, I'll fillet only +the edges I must fillet. + +In outerbrick, I want to fillet the tip of every knob, the side walls, +and the top walls:: + + to_fillet = [] + for count, edge_center in enumerate(outerbrick.subcenters('edge')): + if (abs(edge_center[2]) < 0.1 or + (abs(edge_center[2] - height) < 0.1 and + abs(edge_center[0] - 0.5*unit) % unit < 0.1 and + abs(edge_center[1] - 0.5*unit) % unit < 0.1)): + pass + else: + to_fillet.append(count) + outerbrick.fillet(fillet_rad, to_fillet) + +The loop goes through every edge in **outerbrick**, analyzing the edge's +center. Edges whose z-component is near zero are not filleted. +Additionally, edges with z-component equal to **height** and +xy-components near the knob positions are not filleted. All other +edges are filleted. Here's what I get:: + + v.clear() + v.display(outerbrick) + +.. image:: example1_outerbrick.png + +With the **solidbrick** function, I can now define the inside of the brick:: + + innerbrick = brick.solidbrick(xsize, ysize, wall_thickness, + unit, height - wall_thickness, draft, + knob_rad - wall_thickness, knob_height, + knob_draft) + base = ccad.box(2*unit*xsize, 2*unit*ysize, 1.0) + base.translate((-0.5*unit*xsize, -0.5*unit*ysize, -1.0)) + innerbrick = innerbrick + base + + view1.clear() + view1.display(innerbrick) + +.. image:: example1_innerbrick.png + +Note, I have added a base to **innerbrick**. It will be helpful when I +perform the boolean cut. Finally, I'll fillet **innerbrick**:: + + to_fillet = [] + for count, edge_center in enumerate(innerbrick.subcenters('edge')): + if (abs(edge_center[2]) < 0.1 or + (abs(edge_center[2] - (height - wall_thickness)) < 0.1 and + abs(edge_center[0] - 0.5*unit) % unit < 0.1 and + abs(edge_center[1] - 0.5*unit) % unit < 0.1)): + to_fillet.append(count) + innerbrick.fillet(fillet_rad, to_fillet) + + v.clear() + v.display(innerbrick) + +.. image:: example1_innerbrickfillet.png + +You should recognize the fillet code. It's the opposite of the edges +filleted for **outerbrick**, because it's a core. + +Finally, I'll perform the cut:: + + brick = outerbrick - innerbrick + + v.clear() + v.set_projection((0.0, 0.0, 0.0), + (math.sqrt(0.45), -math.sqrt(0.1), -math.sqrt(0.45)), + (0.0, -1.0, 0.0)) + v.display(brick) + +.. image:: example1_brick.png + +I'm almost there. I now only need the posts that sit between the +knobs to add more holding force. Like the knobs, I'll make the posts +cones and fillet the tips:: + + post_rad = (math.sqrt(2.0)*unit - 2*knob_rad)/2.0 + drad = (height - 0.5*wall_thickness) * math.tan(math.radians(draft)) + post_base_rad = post_rad + drad + post = ccad.cone(post_rad, post_base_rad, height - 0.5*wall_thickness) + post.fillet(fillet_rad, [(0.0, 0.0, 0.0)]) + +.. image:: example1_post.png + +Here, I've chosen to pass a list of the center positions of the edges +to be filleted to the fillet method. In this case, I'm only filleting +one edge, the tip. + +Now, I translate it and add it to the brick:: + + post.translate((unit/2, unit/2, 0.0)) + for x in range(xsize - 1): + for y in range(ysize - 1): + brick = brick + ccad.translated(post, (x * unit, y * unit, 0.0)) + +.. image:: example1_brickpost.png diff --git a/src/contrib/ccad/doc/face_face_from.png b/src/contrib/ccad/doc/face_face_from.png new file mode 100644 index 000000000..54d6d5fd0 Binary files /dev/null and b/src/contrib/ccad/doc/face_face_from.png differ diff --git a/src/contrib/ccad/doc/face_filling.png b/src/contrib/ccad/doc/face_filling.png new file mode 100644 index 000000000..2a5a6ee33 Binary files /dev/null and b/src/contrib/ccad/doc/face_filling.png differ diff --git a/src/contrib/ccad/doc/face_plane.png b/src/contrib/ccad/doc/face_plane.png new file mode 100644 index 000000000..e464e0fc4 Binary files /dev/null and b/src/contrib/ccad/doc/face_plane.png differ diff --git a/src/contrib/ccad/doc/face_surface.png b/src/contrib/ccad/doc/face_surface.png new file mode 100644 index 000000000..66ed11909 Binary files /dev/null and b/src/contrib/ccad/doc/face_surface.png differ diff --git a/src/contrib/ccad/doc/file_transfer.rst b/src/contrib/ccad/doc/file_transfer.rst new file mode 100644 index 000000000..7d8d59dea --- /dev/null +++ b/src/contrib/ccad/doc/file_transfer.rst @@ -0,0 +1,89 @@ +File Transfer +============= + +Sometimes, you'll want to save and restore data. Also, you'll want to +transfer designs with the outside world. ccad offers the following +features. + +to_brep / from_brep +------------------- + +All shapes offers a to_brep method to store a shape in OCC's native +format:: + + s1 = cm.sphere(1.0) + s1.to_brep('sphere.brep') + +ccad also offers the function from_brep to import a .brep file:: + + s1 = ccad.from_brep('sphere.brep') + +to_iges / from_iges +------------------- + +All shapes offer a to_iges method to store shapes in IGES format:: + + s1 = cm.sphere(1.0) + s1.to_iges('sphere.igs') + +ccad also offers the function from_iges to import an IGES file:: + + s1 = ccad.from_iges('sphere.igs') + +There is one slight nuance to this example: the shape type changed:: + + s1.stype + 'face' + +To fix, use the *brep_mode* pass parameter:: + + s1.to_iges('sphere.igs', brep_mode = 1) + s1 = ccad.from_iges('sphere.igs') + s1.stype + 'solid' + +to_step / from_step +------------------- + +All shapes offer a to_step method to store shapes in STEP format:: + + s1 = cm.sphere(1.0) + s1.to_step('sphere.stp') + +ccad also offers the function from_step to import a STEP file:: + + s1 = ccad.from_step('sphere.stp') + +to_stl +------ + +Solids offer a to_stl method to store solids in STL format:: + + s1 = cm.sphere(1.0) + s1.to_stl('sphere.stl') + +ccad does not offer a from_stl function. + +from_svg +-------- + +ccad offers a from_svg function to import a two dimensional SVG file. +It converts each svg path into a wire and returns a list of wires:: + + ws = ccad.from_svg('logo.svg') + for w in ws: + view1.display(w) + +The .svg file + +.. image:: logo.png + +ccad's conversion of it + +.. image:: from_svg.png + +Notes +----- + +Many of these methods and functions offer considerably more conversion +options than shown. Consult the API for all options. diff --git a/src/contrib/ccad/doc/from_svg.png b/src/contrib/ccad/doc/from_svg.png new file mode 100644 index 000000000..2723da73c Binary files /dev/null and b/src/contrib/ccad/doc/from_svg.png differ diff --git a/src/contrib/ccad/doc/generate_images.py b/src/contrib/ccad/doc/generate_images.py new file mode 100644 index 000000000..2a1c685b3 --- /dev/null +++ b/src/contrib/ccad/doc/generate_images.py @@ -0,0 +1,658 @@ +""" +Generates the images for ccad documentation. The screen shots must be +generated manually. Do so with: + +xwd -frame -out name.xwd + +where name is the filename. Convert the image to .png with gimp. +""" + + +import sys +import math + +import ccad +import brick_images + +# Globals +v = ccad.view() + +# Helper Functions +def save_iso(shape, name, color=None): + v.viewstandard(viewtype = 'iso') + v.display(shape, color) + if shape.stype == 'solid': + edges = shape.subshapes('edge') + for edge in edges: + v.display(edge, (0.0, 0.0, 0.0)) + v.fit() + v.save(name) + v.clear() + +def save_top(shape, name, color=None): + v.viewstandard(viewtype = 'top') + v.display(shape, color) + v.fit() + v.save(name) + v.clear() + +def sphere_example(): + s1 = ccad.sphere(10.0) + save_iso(s1, 'sphere_example.png') + +def _cube_subs(cube, subs, name): + v.viewstandard(viewtype = 'iso') + v.display(cube) + subs_colors = [(1.0, 0.0, 0.0), + (0.0, 1.0, 0.0), + (0.0, 0.0, 1.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 1.0), + (1.0, 0.0, 1.0)] + for count in range(len(subs)): + v.display(subs[count], subs_colors[count % 6]) + v.fit() + v.save(name) + v.clear() + +def _cube_sub(cube, sub, name): + v.viewstandard(viewtype = 'iso') + v.display(cube) + edges = cube.subshapes('edge') + for edge in edges: + v.display(edge, (0.0, 0.0, 0.0)) + if sub != None: + v.display(sub, (1.0, 0.0, 0.0), line_width = 3.0) + v.fit() + v.save(name) + v.clear() + +def cube_solid(): + s1 = ccad.box(1.0, 1.0, 1.0) + #_cube_subs(s1, [], 'cube_solid.png') + _cube_sub(s1, None, 'cube_solid.png') + +def cube_face(): + s1 = ccad.box(1.0, 1.0, 1.0) + subs = s1.subshapes('face') + #_cube_subs(s1, subs, 'cube_face.png') + _cube_sub(s1, subs[s1.nearest('face', [(1.0, 0.5, 0.5)])[0]], 'cube_face.png') + +def cube_wire(): + s1 = ccad.box(1.0, 1.0, 1.0) + faces = s1.subshapes('face') + face = faces[s1.nearest('face', [(1.0, 0.5, 0.5)])[0]] + w1 = face.subshapes('wire')[0] + #_cube_subs(s1, subs, 'cube_wire.png') + _cube_sub(s1, w1, 'cube_wire.png') + +def cube_edge(): + s1 = ccad.box(1.0, 1.0, 1.0) + subs = s1.subshapes('edge') + #_cube_subs(s1, subs, 'cube_edge.png') + _cube_sub(s1, subs[s1.nearest('edge', [(1.0, 0.5, 1.0)])[0]], 'cube_edge.png') + +def cube_vertex(): + s1 = ccad.box(1.0, 1.0, 1.0) + subs = s1.subshapes('vertex') + #_cube_subs(s1, subs, 'cube_vertex.png') + _cube_sub(s1, subs[s1.nearest('vertex', [(1.0, 0.0, 1.0)])[0]], 'cube_vertex.png') + +def edge_segment(): + pt1 = (0.0, 0.0, 0.0) + pt2 = (1.0, 0.0, 0.0) + e1 = ccad.segment(pt1, pt2) + save_top(e1, 'edge_segment.png', (0.0, 0.0, 0.0)) + +def edge_arc(): + e1 = ccad.arc(1.0, 0.0, math.pi/2) + save_top(e1, 'edge_arc.png', (0.0, 0.0, 0.0)) + +def edge_arc_ellipse(): + e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2) + save_top(e1, 'edge_arc_ellipse.png', (0.0, 0.0, 0.0)) + +def edge_spline(): + pts = [(0.0, 0.0, 0.0), + (0.2, 0.1, 0.0), + (0.5, 0.2, 0.0), + (-0.5, 0.3, 0.0)] + e1 = ccad.spline(pts) + v.viewstandard(viewtype = 'top') + v.display(e1, (0.0, 0.0, 0.0)) + for pt in pts: + v.display(ccad.vertex(pt), (1.0, 0.0, 0.0)) + v.fit() + v.save('edge_spline.png') + v.clear() + +def edge_bezier(): + e1 = ccad.bezier([(1.0, 0.0, 0.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 0.0)], [1.0, 1.0/math.sqrt(2.0), 1.0]) + save_top(e1, 'edge_bezier.png', (0.0, 0.0, 0.0)) + +def edge_circle(): + e1 = ccad.circle(3.0) + save_top(e1, 'edge_circle.png', (0.0, 0.0, 0.0)) + +def edge_ellipse(): + e1 = ccad.ellipse(2.0, 1.0) + save_top(e1, 'edge_ellipse.png', (0.0, 0.0, 0.0)) + +def wire(): + e1 = ccad.arc(1.0, -math.pi/2, math.pi/2) + e1.translate((1.0, 0.0, 0.0)) + e2 = ccad.segment((1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)) + e3 = ccad.arc(1.0, math.pi/2, 3*math.pi/2) + e3.translate((-1.0, 0.0, 0.0)) + e4 = ccad.segment((-1.0, -1.0, 0.0), (1.0, -1.0, 0.0)) + w1 = ccad.wire([e1, e2, e3, e4]) + save_top(w1, 'wire.png', (0.0, 0.0, 0.0)) + +def wire_polygon(): + w1 = ccad.polygon([(0.0, 0.0, 0.0), + (1.0, 0.0, 0.0), + (1.5, 1.0, 0.0), + (0.5, 1.5, 0.0), + (-0.5, -0.5, 0.0)]) + save_top(w1, 'wire_polygon.png', (0.0, 0.0, 0.0)) + +def wire_rectangle(): + w1 = ccad.rectangle(2.0, 1.0) + save_top(w1, 'wire_rectangle.png', (0.0, 0.0, 0.0)) + +def wire_ngon(): + w1 = ccad.ngon(2.0, 6) + save_top(w1, 'wire_ngon.png', (0.0, 0.0, 0.0)) + +def wire_helix(): + w1 = ccad.helix(2.0, 1.0/math.pi, 3) + save_iso(w1, 'wire_helix.png', (0.0, 0.0, 0.0)) + +def face_plane(): + w1 = ccad.ngon(2.0, 5) + f1 = ccad.plane(w1) + save_top(f1, 'face_plane.png') + +def face_face_from(): + w1 = ccad.ngon(2.0, 8) + w2 = ccad.ngon(10.0, 4) + f2 = ccad.plane(w1) + f1 = ccad.face_from(f2, w2) + save_top(f1, 'face_face_from.png') + +def face_filling(): + e1 = ccad.spline([(0.0, 0.0, 0.0), + (1.0, 0.2, 0.3), + (1.5, 0.8, 1.0), + (0.8, 1.2, 0.2), + (0.0, 1.0, 0.0)]) + e2 = ccad.spline([(0.0, 0.0, 0.0), + (-1.0, 0.2, 0.3), + (-1.5, 0.8, 1.0), + (-0.8, 1.2, 0.2), + (0.0, 1.0, 0.0)]) + w1 = ccad.wire([e1, e2]) + f1 = ccad.filling(w1) + v.viewstandard(viewtype = 'iso') + v.display(w1, color = (1.0, 0.0, 0.0)) + v.display(f1) + v.fit() + v.save('face_filling.png') + v.clear() + +def solid_box(): + s1 = ccad.box(1.0, 2.0, 3.0) + save_iso(s1, 'solid_box.png') + +def solid_wedge(): + s1 = ccad.wedge(1.0, 2.0, 3.0, 0.5) + save_iso(s1, 'solid_wedge.png') + +def solid_cylinder(): + s1 = ccad.cylinder(1.0, 2.0) + save_iso(s1, 'solid_cylinder.png') + +def solid_sphere(): + s1 = ccad.sphere(5.0) + save_iso(s1, 'solid_sphere.png') + +def solid_cone(): + s1 = ccad.cone(4.0, 2.0, 2.0) + save_iso(s1, 'solid_cone.png') + +def solid_torus(): + s1 = ccad.torus(10.0, 1.0) + save_iso(s1, 'solid_torus.png') + +def derived_prism(): + f1 = ccad.plane(ccad.ngon(2.0, 6)) + s1 = ccad.prism(f1, (0.0, 0.0, 1.0)) + save_iso(s1, 'derived_prism.png') + +def derived_revol(): + e1 = ccad.arc(1.0, -math.pi/2, math.pi/2) + e1.translate((3.0, 0.0, 0.0)) + w1 = ccad.polygon([(3.0, 1.0, 0.0), + (2.0, 1.0, 0.0), + (2.0, -1.0, 0.0), + (3.0, -1.0, 0.0)]) + f1 = ccad.plane(ccad.wire([e1, w1])) + f1.rotatex(math.pi/2) + s1 = ccad.revol(f1, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), 2*math.pi) + save_iso(s1, 'derived_revol.png') + +def derived_loft(): + w1 = ccad.wire([ccad.circle(1.0)]) + w2 = ccad.wire([ccad.circle(2.0)]) + w2.translate((0.0, 0.0, 5.0)) + w3 = ccad.wire([ccad.circle(1.5)]) + w3.translate((0.0, 0.0, 10.0)) + s1 = ccad.loft([w1, w2, w3]) + save_iso(s1, 'derived_loft.png') + +def derived_pipe(): + profile = ccad.ngon(2.0, 6) + e1 = ccad.arc(8.0, 0.0, math.pi/2) + e2 = ccad.segment((0.0, 8.0, 0.0), (-8.0, 8.0, 0.0)) + spine = ccad.wire([e1, e2]) + spine.translate((-8.0, 0.0, 0.0)) + spine.rotatex(math.pi/2) + s1 = ccad.pipe(profile, spine) + save_iso(s1, 'derived_pipe.png') + +def derived_helical_solid(): + profile = ccad.ngon(0.2, 3) + s1 = ccad.helical_solid(profile, 2.0, 1.0/math.pi, 2) + save_iso(s1, 'derived_helical_solid.png') + +def derived_offset_face(): + w1 = ccad.ngon(8.0, 6) + f1 = ccad.offset(ccad.plane(w1), 1.0)[0] + v.viewstandard(viewtype = 'top') + v.display(w1, (0.0, 0.0, 0.0)) + v.display(f1) + v.fit() + v.save('derived_offset_face.png') + v.clear() + +def derived_offset_solid(): + b1 = ccad.box(10.0, 10.0, 10.0) + b1.translate((-5.0, -5.0, 0.0)) + c1 = ccad.cylinder(2.5, 20.0) + c1.translate((0.0, 0.0, -5.0)) + s1 = b1 - c1 + s2 = ccad.offset(s1, 1.0)[0] + v.viewstandard(viewtype = 'iso') + v.display(s1, (1.0, 0.0, 0.0)) + edges = s1.subshapes('edge') + for edge in edges: + v.display(edge, (0.0, 0.0, 0.0)) + v.display(s2, transparency = 0.5) + v.fit() + v.save('derived_offset_solid.png') + v.clear() + +def derived_slice(): + b1 = ccad.box(10.0, 10.0, 10.0) + b1.translate((-5.0, -5.0, 0.0)) + c1 = ccad.cylinder(2.5, 20.0) + c1.translate((0.0, 0.0, -5.0)) + s1 = b1 - c1 + f1 = ccad.slice(s1, z = 1.0)[0] + v.viewstandard(viewtype = 'top') + v.display(f1) + v.fit() + v.save('derived_slice.png') + v.clear() + +def trms_translate(): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.translated(s1, (2.0, -6.0, 4.0)) + v.viewstandard(viewtype = 'iso') + v.display(s1, (1.0, 0.0, 0.0)) + v.display(s2, (0.0, 0.0, 1.0)) + v.fit() + v.save('trms_translate.png') + v.clear() + +def trms_rotate(): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.rotated(s1, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), math.pi/2) + v.viewstandard(viewtype = 'iso') + v.display(s1, (1.0, 0.0, 0.0)) + v.display(s2, (0.0, 0.0, 1.0)) + v.fit() + v.save('trms_rotate.png') + v.clear() + +def trms_mirror(): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.mirrored(s1, (0.0, 0.0, 0.0), (1.0, 0.0, 0.0)) + v.viewstandard(viewtype = 'iso') + v.display(s1, (1.0, 0.0, 0.0)) + v.display(s2, (0.0, 0.0, 1.0)) + v.fit() + v.save('trms_mirror.png') + v.clear() + +def trms_scale(): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.scaled(s1, 2.0, 1.0, 1.0) + s2.translate((4.0, 0.0, 0.0)) + v.viewstandard(viewtype = 'iso') + v.display(s1, (1.0, 0.0, 0.0)) + v.display(s2, (0.0, 0.0, 1.0)) + v.fit() + v.save('trms_scale.png') + v.clear() + +def boolean_fuse(): + s1 = ccad.sphere(1.0) + s2 = ccad.box(1.0, 2.0, 3.0) + s3 = s1 + s2 + save_iso(s3, 'boolean_fuse.png') + +def boolean_cut(): + s1 = ccad.sphere(1.0) + s2 = ccad.box(1.0, 2.0, 3.0) + s3 = s1 - s2 + save_iso(s3, 'boolean_cut.png') + +def boolean_common(): + s1 = ccad.sphere(1.0) + s2 = ccad.box(1.0, 2.0, 3.0) + s3 = s1 & s2 + save_iso(s3, 'boolean_common.png') + +def boolean_glue(): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.box(1.0, 1.0, 1.0) + s2.translate((1.0, 0.5, 1.0)) + faces_s1 = s1.subshapes('face') + faces_s2 = s2.subshapes('face') + i1 = s1.nearest('face', [(1.0, 1.0, 1.5)])[0] + i2 = s2.nearest('face', [(1.0, 1.0, 1.5)])[0] + print 'glue faces', i1, i2 + s3 = ccad.glue(s1, s2, [(i1, i2)]) + save_iso(s3, 'boolean_glue.png') + +def logging_face_fillet(): + w1 = ccad.polygon([(-1.0, -1.0, 0.0), + (1.0, -1.0, 0.0), + (1.0, 1.0, 0.0), + (-1.0, 1.0, 0.0), + (-1.0, -1.0, 0.0)]) + f1 = ccad.plane(w1) + f1.fillet(0.25, f1.nearest('vertex', [(1.0, 1.0, 0.0), (-1.0, -1.0, 0.0)])) + save_top(f1, 'logging_face_fillet.png') + +def logging_solid_fillet(): + s1 = ccad.box(1.0, 1.0, 1.0) + s1.fillet(0.25, [(1.0, 0.5, 0.0), + (1.0, 0.5, 1.0), + (1.0, 0.0, 0.5), + (1.0, 1.0, 0.5)]) + save_iso(s1, 'logging_solid_fillet.png') + +def logging_solid_chamfer(): + s1 = ccad.box(1.0, 1.0, 1.0) + s1.chamfer(0.25, [(1.0, 0.5, 0.0), + (1.0, 0.5, 1.0), + (1.0, 0.0, 0.5), + (1.0, 1.0, 0.5)]) + save_iso(s1, 'logging_solid_chamfer.png') + +def logging_solid_draft(): + s1 = ccad.box(1.0, 1.0, 1.0) + s1.translate((-0.5, -0.5, 0.0)) + face_centers = s1.subcenters('face') + to_draft = [] + for count, face_center in enumerate(face_centers): + if abs(face_center[2] - 0.5) < 0.1: + to_draft.append(count) + s1.draft(math.radians(5.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0), to_draft) + save_iso(s1, 'logging_solid_draft.png') + +def logging_solid_simplify(): + s1 = ccad.box(1.0, 1.0, 1.0) + s2 = s1.copy() + s2.translate((1.0, 0.5, 0.5)) + s3 = s1 - s2 + print len(s3.subshapes('face')) + save_iso(s3, 'logging_solid_simplify1.png') + s3.simplify() + print len(s3.subshapes('face')) + save_iso(s3, 'logging_solid_simplify2.png') + +def from_svg(): + v.viewstandard(viewtype = 'iso') + ws = ccad.from_svg('logo.svg') + for w in ws: + v.display(w, (0.0, 0.0, 0.0)) + v.fit() + v.save('from_svg.png') + v.clear() + +def display_display(): + s1 = ccad.sphere(1.0) + ccad.box(1.0, 2.0, 2.0) + v.display(s1, color = (1.0, 0.0, 0.0), material = 'metallized', transparency = 0.5) + v.fit() + v.save('display_display.png') + v.clear() + +def solidbrick(xsize, ysize, wall_offset, unit, height, draft, knob_rad, knob_height, knob_draft, save_images = 0): + + dx = height * math.tan(math.radians(draft)) + wbottom = ccad.rectangle(unit * xsize - 2*wall_offset, unit * ysize - 2*wall_offset) + wtop = ccad.rectangle(unit * xsize - 2*dx - 2*wall_offset, unit * ysize - 2*dx - 2*wall_offset) + wtop.translate((dx, dx, height)) + brick = ccad.loft([wbottom, wtop], True) + brick.translate((wall_offset, wall_offset, 0.0)) + + if save_images: + v.display(brick) + v.fit() + v.save('example1_box.png') + + drad = knob_height * math.tan(math.radians(knob_draft)) + knob_top_rad = knob_rad - drad + knob_base_rad = knob_rad + drad + knob = ccad.cone(knob_base_rad, knob_top_rad, 2 * knob_height) + knob.translate((0.5 * unit, 0.5 * unit, height - knob_height)) + + if save_images: + v.display(knob, color = (1.0, 0.0, 0.0)) + v.fit() + v.save('example1_boxwknob.png') + + for x in range(xsize): + for y in range(ysize): + brick = brick + ccad.translated(knob, (x * unit, y * unit, 0.0)) + + if save_images: + v.clear() + v.display(brick) + v.save('example1_boxwknobs.png') + + return brick + +def example1(): + import numpy as np + + unit = 5.0 + height = 10.0 + draft = 1.0 # degrees of draft on faces for plastic ejection + knob_rad = 1.8 # radius of the brick knob for mating with other bricks + knob_height = 2.0 + knob_draft = 5.0 # degrees of draft for the knob + wall_thickness = 1.0 # plastic wall thickness + fillet_rad = 0.4 # the default radius to use for rounded edges + + xsize = 4 + ysize = 2 + + outerbrick = solidbrick(xsize, ysize, 0.0, unit, height, draft, knob_rad, knob_height, knob_draft, save_images = 1) + + to_fillet = [] + for count, edge_center in enumerate(outerbrick.subcenters('edge')): + if (abs(edge_center[2]) < 0.1 or + (abs(edge_center[2] - height) < 0.1 and + abs(edge_center[0] - 0.5*unit) % unit < 0.1 and + abs(edge_center[1] - 0.5*unit) % unit < 0.1)): + pass + else: + to_fillet.append(count) + outerbrick.fillet(fillet_rad, to_fillet) + + v.clear() + v.display(outerbrick) + v.fit() + v.save('example1_outerbrick.png') + + innerbrick = solidbrick(xsize, ysize, wall_thickness, unit, height - wall_thickness, draft, knob_rad - wall_thickness, knob_height, knob_draft) + base = ccad.box(2*unit*xsize, 2*unit*ysize, 1.0) + base.translate((-0.5*unit*xsize, -0.5*unit*ysize, -1.0)) + innerbrick = innerbrick + base + + v.clear() + v.display(innerbrick) + v.fit() + v.save('example1_innerbrick.png') + + to_fillet = [] + for count, edge_center in enumerate(innerbrick.subcenters('edge')): + if (abs(edge_center[2]) < 0.1 or + (abs(edge_center[2] - (height - wall_thickness)) < 0.1 and + abs(edge_center[0] - 0.5*unit) % unit < 0.1 and + abs(edge_center[1] - 0.5*unit) % unit < 0.1)): + to_fillet.append(count) + innerbrick.fillet(fillet_rad, to_fillet) + + v.clear() + v.display(innerbrick) + v.fit() + v.save('example1_innerbrickfillet.png') + + brick = outerbrick - innerbrick + + v.clear() + v.display(brick) + v.set_projection((0.0, 0.0, 0.0), (math.sqrt(0.45), -math.sqrt(0.1), -math.sqrt(0.45)), (0.0, -1.0, 0.0)) + v.fit() + v.save('example1_brick.png') + + post_rad = (math.sqrt(2.0)*unit - 2*knob_rad)/2.0 + drad = (height - 0.5*wall_thickness) * math.tan(math.radians(draft)) + post_base_rad = post_rad + drad + post = ccad.cone(post_rad, post_base_rad, height - 0.5*wall_thickness) + post.fillet(fillet_rad, [(0.0, 0.0, 0.0)]) + + v.clear() + v.display(post) + v.viewstandard(viewtype = 'iso') + v.fit() + v.save('example1_post.png') + + post.translate((unit, unit, 0.0)) + for x in range(xsize - 1): + for y in range(ysize - 1): + brick = brick + ccad.translated(post, (x * unit, y * unit, 0.0)) + + v.clear() + v.display(brick) + v.viewstandard(viewtype = 'bottom') + v.fit() + v.save('example1_brickpost.png') + + brick_images.generate_images(4, 2) + +def main(): + v.set_background((1.0, 1.0, 1.0)) + v.set_triedron(1, color = (0.0, 0.0, 0.0)) + + # Quick code for a single one + #face_face_from() + #sys.exit() + + # intro + #sphere_example() # Moved to screen capture + + # modelling + cube_solid() + cube_face() + cube_wire() + cube_edge() + cube_vertex() + + # creating_edges + edge_segment() + edge_arc() + edge_arc_ellipse() + edge_spline() + edge_bezier() + edge_circle() + edge_ellipse() + + # creating_wires + wire() + wire_polygon() + wire_rectangle() + wire_ngon() + wire_helix() + + # creating_faces + face_plane() + face_face_from() + face_filling() + + # creating_solids + solid_box() + solid_wedge() + solid_cylinder() + solid_sphere() + solid_cone() + solid_torus() + + # creating_derived + derived_prism() + derived_revol() + derived_loft() + # plane_loft skipped, since we hope to remove it someday + derived_pipe() + derived_helical_solid() + derived_offset_face() + derived_offset_solid() + derived_slice() + + # trms + trms_translate() + trms_rotate() + trms_mirror() + trms_scale() + + # boolean + boolean_fuse() + boolean_cut() + boolean_common() + boolean_glue() + + # logging + logging_face_fillet() + logging_solid_fillet() + logging_solid_chamfer() + logging_solid_draft() + logging_solid_simplify() + + # file transfer + from_svg() + + # display + display_display() + + # examples + example1() + +if __name__ == '__main__': + main() diff --git a/src/contrib/ccad/doc/html/.buildinfo b/src/contrib/ccad/doc/html/.buildinfo new file mode 100644 index 000000000..2968d0f57 --- /dev/null +++ b/src/contrib/ccad/doc/html/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 83d0b22a16e3ba0021b5ec9e2f9b2fe5 +tags: fbb0d17656682115ca4d033fb2f83ba1 diff --git a/src/contrib/ccad/doc/html/.doctrees/boolean.doctree b/src/contrib/ccad/doc/html/.doctrees/boolean.doctree new file mode 100644 index 000000000..1272dd695 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/boolean.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/configuring_display.doctree b/src/contrib/ccad/doc/html/.doctrees/configuring_display.doctree new file mode 100644 index 000000000..ec862975f Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/configuring_display.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/contents.doctree b/src/contrib/ccad/doc/html/.doctrees/contents.doctree new file mode 100644 index 000000000..5efea1137 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/contents.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/controlling_display.doctree b/src/contrib/ccad/doc/html/.doctrees/controlling_display.doctree new file mode 100644 index 000000000..f03bb70bb Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/controlling_display.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/creating_derived.doctree b/src/contrib/ccad/doc/html/.doctrees/creating_derived.doctree new file mode 100644 index 000000000..fc25217ba Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/creating_derived.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/creating_edges.doctree b/src/contrib/ccad/doc/html/.doctrees/creating_edges.doctree new file mode 100644 index 000000000..f16c6727f Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/creating_edges.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/creating_faces.doctree b/src/contrib/ccad/doc/html/.doctrees/creating_faces.doctree new file mode 100644 index 000000000..0f786916c Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/creating_faces.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/creating_solids.doctree b/src/contrib/ccad/doc/html/.doctrees/creating_solids.doctree new file mode 100644 index 000000000..0bdc5a165 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/creating_solids.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/creating_wires.doctree b/src/contrib/ccad/doc/html/.doctrees/creating_wires.doctree new file mode 100644 index 000000000..4c73784cf Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/creating_wires.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/details.doctree b/src/contrib/ccad/doc/html/.doctrees/details.doctree new file mode 100644 index 000000000..66b1da901 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/details.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/displaying.doctree b/src/contrib/ccad/doc/html/.doctrees/displaying.doctree new file mode 100644 index 000000000..abd6f8f5e Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/displaying.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/environment.pickle b/src/contrib/ccad/doc/html/.doctrees/environment.pickle new file mode 100644 index 000000000..6790592aa Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/environment.pickle differ diff --git a/src/contrib/ccad/doc/html/.doctrees/example1.doctree b/src/contrib/ccad/doc/html/.doctrees/example1.doctree new file mode 100644 index 000000000..b17440472 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/example1.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/example1_brickpy.doctree b/src/contrib/ccad/doc/html/.doctrees/example1_brickpy.doctree new file mode 100644 index 000000000..fad975b0b Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/example1_brickpy.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/example1_drawings.doctree b/src/contrib/ccad/doc/html/.doctrees/example1_drawings.doctree new file mode 100644 index 000000000..a90400cca Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/example1_drawings.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/example1_multibrick.doctree b/src/contrib/ccad/doc/html/.doctrees/example1_multibrick.doctree new file mode 100644 index 000000000..3f06847dd Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/example1_multibrick.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/example1_nuances.doctree b/src/contrib/ccad/doc/html/.doctrees/example1_nuances.doctree new file mode 100644 index 000000000..e51382e07 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/example1_nuances.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/example1_singlebrick.doctree b/src/contrib/ccad/doc/html/.doctrees/example1_singlebrick.doctree new file mode 100644 index 000000000..2365bf81a Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/example1_singlebrick.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/file_transfer.doctree b/src/contrib/ccad/doc/html/.doctrees/file_transfer.doctree new file mode 100644 index 000000000..c58c40680 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/file_transfer.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/interactive.doctree b/src/contrib/ccad/doc/html/.doctrees/interactive.doctree new file mode 100644 index 000000000..5d2bce669 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/interactive.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/intro.doctree b/src/contrib/ccad/doc/html/.doctrees/intro.doctree new file mode 100644 index 000000000..85150e809 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/intro.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/logging.doctree b/src/contrib/ccad/doc/html/.doctrees/logging.doctree new file mode 100644 index 000000000..839295bc5 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/logging.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/modelling.doctree b/src/contrib/ccad/doc/html/.doctrees/modelling.doctree new file mode 100644 index 000000000..9bb105a29 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/modelling.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/stand_alone.doctree b/src/contrib/ccad/doc/html/.doctrees/stand_alone.doctree new file mode 100644 index 000000000..c249f2edf Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/stand_alone.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/trms.doctree b/src/contrib/ccad/doc/html/.doctrees/trms.doctree new file mode 100644 index 000000000..54d7b539a Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/trms.doctree differ diff --git a/src/contrib/ccad/doc/html/.doctrees/using_display.doctree b/src/contrib/ccad/doc/html/.doctrees/using_display.doctree new file mode 100644 index 000000000..9d0967627 Binary files /dev/null and b/src/contrib/ccad/doc/html/.doctrees/using_display.doctree differ diff --git a/src/contrib/ccad/doc/html/_images/boolean_common.png b/src/contrib/ccad/doc/html/_images/boolean_common.png new file mode 100644 index 000000000..0bfc834c1 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/boolean_common.png differ diff --git a/src/contrib/ccad/doc/html/_images/boolean_cut.png b/src/contrib/ccad/doc/html/_images/boolean_cut.png new file mode 100644 index 000000000..cf6b20024 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/boolean_cut.png differ diff --git a/src/contrib/ccad/doc/html/_images/boolean_fuse.png b/src/contrib/ccad/doc/html/_images/boolean_fuse.png new file mode 100644 index 000000000..79e74de53 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/boolean_fuse.png differ diff --git a/src/contrib/ccad/doc/html/_images/boolean_glue.png b/src/contrib/ccad/doc/html/_images/boolean_glue.png new file mode 100644 index 000000000..bc6dc1309 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/boolean_glue.png differ diff --git a/src/contrib/ccad/doc/html/_images/brick_bottom.png b/src/contrib/ccad/doc/html/_images/brick_bottom.png new file mode 100644 index 000000000..156dce5e7 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/brick_bottom.png differ diff --git a/src/contrib/ccad/doc/html/_images/brick_iso.png b/src/contrib/ccad/doc/html/_images/brick_iso.png new file mode 100644 index 000000000..7bc664e33 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/brick_iso.png differ diff --git a/src/contrib/ccad/doc/html/_images/brick_side.png b/src/contrib/ccad/doc/html/_images/brick_side.png new file mode 100644 index 000000000..bae2fe78a Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/brick_side.png differ diff --git a/src/contrib/ccad/doc/html/_images/brick_top.png b/src/contrib/ccad/doc/html/_images/brick_top.png new file mode 100644 index 000000000..d000ed46d Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/brick_top.png differ diff --git a/src/contrib/ccad/doc/html/_images/cube_edge.png b/src/contrib/ccad/doc/html/_images/cube_edge.png new file mode 100644 index 000000000..478023726 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/cube_edge.png differ diff --git a/src/contrib/ccad/doc/html/_images/cube_face.png b/src/contrib/ccad/doc/html/_images/cube_face.png new file mode 100644 index 000000000..c78af4604 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/cube_face.png differ diff --git a/src/contrib/ccad/doc/html/_images/cube_solid.png b/src/contrib/ccad/doc/html/_images/cube_solid.png new file mode 100644 index 000000000..3786ad086 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/cube_solid.png differ diff --git a/src/contrib/ccad/doc/html/_images/cube_vertex.png b/src/contrib/ccad/doc/html/_images/cube_vertex.png new file mode 100644 index 000000000..4610014e3 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/cube_vertex.png differ diff --git a/src/contrib/ccad/doc/html/_images/cube_wire.png b/src/contrib/ccad/doc/html/_images/cube_wire.png new file mode 100644 index 000000000..5365c37ba Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/cube_wire.png differ diff --git a/src/contrib/ccad/doc/html/_images/derived_helical_solid.png b/src/contrib/ccad/doc/html/_images/derived_helical_solid.png new file mode 100644 index 000000000..ca5f332e0 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/derived_helical_solid.png differ diff --git a/src/contrib/ccad/doc/html/_images/derived_loft.png b/src/contrib/ccad/doc/html/_images/derived_loft.png new file mode 100644 index 000000000..72675bdb5 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/derived_loft.png differ diff --git a/src/contrib/ccad/doc/html/_images/derived_offset_face.png b/src/contrib/ccad/doc/html/_images/derived_offset_face.png new file mode 100644 index 000000000..f8263a780 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/derived_offset_face.png differ diff --git a/src/contrib/ccad/doc/html/_images/derived_offset_solid.png b/src/contrib/ccad/doc/html/_images/derived_offset_solid.png new file mode 100644 index 000000000..e6189a2b1 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/derived_offset_solid.png differ diff --git a/src/contrib/ccad/doc/html/_images/derived_pipe.png b/src/contrib/ccad/doc/html/_images/derived_pipe.png new file mode 100644 index 000000000..e8ef5e994 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/derived_pipe.png differ diff --git a/src/contrib/ccad/doc/html/_images/derived_prism.png b/src/contrib/ccad/doc/html/_images/derived_prism.png new file mode 100644 index 000000000..f33306d97 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/derived_prism.png differ diff --git a/src/contrib/ccad/doc/html/_images/derived_revol.png b/src/contrib/ccad/doc/html/_images/derived_revol.png new file mode 100644 index 000000000..608fa448d Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/derived_revol.png differ diff --git a/src/contrib/ccad/doc/html/_images/derived_slice.png b/src/contrib/ccad/doc/html/_images/derived_slice.png new file mode 100644 index 000000000..207478996 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/derived_slice.png differ diff --git a/src/contrib/ccad/doc/html/_images/display_display.png b/src/contrib/ccad/doc/html/_images/display_display.png new file mode 100644 index 000000000..679a30144 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/display_display.png differ diff --git a/src/contrib/ccad/doc/html/_images/display_hlr.png b/src/contrib/ccad/doc/html/_images/display_hlr.png new file mode 100644 index 000000000..032ee223c Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/display_hlr.png differ diff --git a/src/contrib/ccad/doc/html/_images/display_shaded.png b/src/contrib/ccad/doc/html/_images/display_shaded.png new file mode 100644 index 000000000..940fbbb5b Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/display_shaded.png differ diff --git a/src/contrib/ccad/doc/html/_images/display_spherebox1.png b/src/contrib/ccad/doc/html/_images/display_spherebox1.png new file mode 100644 index 000000000..647b66daa Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/display_spherebox1.png differ diff --git a/src/contrib/ccad/doc/html/_images/display_wireframe.png b/src/contrib/ccad/doc/html/_images/display_wireframe.png new file mode 100644 index 000000000..c1e887999 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/display_wireframe.png differ diff --git a/src/contrib/ccad/doc/html/_images/edge_arc.png b/src/contrib/ccad/doc/html/_images/edge_arc.png new file mode 100644 index 000000000..312bb0c1d Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/edge_arc.png differ diff --git a/src/contrib/ccad/doc/html/_images/edge_arc_ellipse.png b/src/contrib/ccad/doc/html/_images/edge_arc_ellipse.png new file mode 100644 index 000000000..f6ccaffcb Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/edge_arc_ellipse.png differ diff --git a/src/contrib/ccad/doc/html/_images/edge_bezier.png b/src/contrib/ccad/doc/html/_images/edge_bezier.png new file mode 100644 index 000000000..bd4ba4b59 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/edge_bezier.png differ diff --git a/src/contrib/ccad/doc/html/_images/edge_circle.png b/src/contrib/ccad/doc/html/_images/edge_circle.png new file mode 100644 index 000000000..7998ce3f6 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/edge_circle.png differ diff --git a/src/contrib/ccad/doc/html/_images/edge_ellipse.png b/src/contrib/ccad/doc/html/_images/edge_ellipse.png new file mode 100644 index 000000000..1c0a4d523 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/edge_ellipse.png differ diff --git a/src/contrib/ccad/doc/html/_images/edge_segment.png b/src/contrib/ccad/doc/html/_images/edge_segment.png new file mode 100644 index 000000000..2be996198 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/edge_segment.png differ diff --git a/src/contrib/ccad/doc/html/_images/edge_spline.png b/src/contrib/ccad/doc/html/_images/edge_spline.png new file mode 100644 index 000000000..1da897465 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/edge_spline.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_all_bricks.png b/src/contrib/ccad/doc/html/_images/example1_all_bricks.png new file mode 100644 index 000000000..b2baee82b Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_all_bricks.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_box.png b/src/contrib/ccad/doc/html/_images/example1_box.png new file mode 100644 index 000000000..800b142d6 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_box.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_boxwknob.png b/src/contrib/ccad/doc/html/_images/example1_boxwknob.png new file mode 100644 index 000000000..713ad1bb2 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_boxwknob.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_boxwknobs.png b/src/contrib/ccad/doc/html/_images/example1_boxwknobs.png new file mode 100644 index 000000000..766b7480c Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_boxwknobs.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_brick.png b/src/contrib/ccad/doc/html/_images/example1_brick.png new file mode 100644 index 000000000..b17505efb Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_brick.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_brickpost.png b/src/contrib/ccad/doc/html/_images/example1_brickpost.png new file mode 100644 index 000000000..65136ab52 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_brickpost.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_innerbrick.png b/src/contrib/ccad/doc/html/_images/example1_innerbrick.png new file mode 100644 index 000000000..00c78c697 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_innerbrick.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_innerbrickfillet.png b/src/contrib/ccad/doc/html/_images/example1_innerbrickfillet.png new file mode 100644 index 000000000..b6a885154 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_innerbrickfillet.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_outerbrick.png b/src/contrib/ccad/doc/html/_images/example1_outerbrick.png new file mode 100644 index 000000000..9ffa4cfc0 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_outerbrick.png differ diff --git a/src/contrib/ccad/doc/html/_images/example1_post.png b/src/contrib/ccad/doc/html/_images/example1_post.png new file mode 100644 index 000000000..01a0260d4 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/example1_post.png differ diff --git a/src/contrib/ccad/doc/html/_images/face_face_from.png b/src/contrib/ccad/doc/html/_images/face_face_from.png new file mode 100644 index 000000000..54d6d5fd0 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/face_face_from.png differ diff --git a/src/contrib/ccad/doc/html/_images/face_filling.png b/src/contrib/ccad/doc/html/_images/face_filling.png new file mode 100644 index 000000000..2a5a6ee33 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/face_filling.png differ diff --git a/src/contrib/ccad/doc/html/_images/face_plane.png b/src/contrib/ccad/doc/html/_images/face_plane.png new file mode 100644 index 000000000..e464e0fc4 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/face_plane.png differ diff --git a/src/contrib/ccad/doc/html/_images/from_svg.png b/src/contrib/ccad/doc/html/_images/from_svg.png new file mode 100644 index 000000000..2723da73c Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/from_svg.png differ diff --git a/src/contrib/ccad/doc/html/_images/logging_face_fillet.png b/src/contrib/ccad/doc/html/_images/logging_face_fillet.png new file mode 100644 index 000000000..c21053cad Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/logging_face_fillet.png differ diff --git a/src/contrib/ccad/doc/html/_images/logging_solid_chamfer.png b/src/contrib/ccad/doc/html/_images/logging_solid_chamfer.png new file mode 100644 index 000000000..77ca72a82 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/logging_solid_chamfer.png differ diff --git a/src/contrib/ccad/doc/html/_images/logging_solid_draft.png b/src/contrib/ccad/doc/html/_images/logging_solid_draft.png new file mode 100644 index 000000000..5ecb5fd04 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/logging_solid_draft.png differ diff --git a/src/contrib/ccad/doc/html/_images/logging_solid_fillet.png b/src/contrib/ccad/doc/html/_images/logging_solid_fillet.png new file mode 100644 index 000000000..154c5933c Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/logging_solid_fillet.png differ diff --git a/src/contrib/ccad/doc/html/_images/logging_solid_simplify1.png b/src/contrib/ccad/doc/html/_images/logging_solid_simplify1.png new file mode 100644 index 000000000..4b7745f27 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/logging_solid_simplify1.png differ diff --git a/src/contrib/ccad/doc/html/_images/logging_solid_simplify2.png b/src/contrib/ccad/doc/html/_images/logging_solid_simplify2.png new file mode 100644 index 000000000..3786ad086 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/logging_solid_simplify2.png differ diff --git a/src/contrib/ccad/doc/html/_images/logo.png b/src/contrib/ccad/doc/html/_images/logo.png new file mode 100644 index 000000000..28c5cfe3d Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/logo.png differ diff --git a/src/contrib/ccad/doc/html/_images/select_edge.png b/src/contrib/ccad/doc/html/_images/select_edge.png new file mode 100644 index 000000000..fed7b7767 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/select_edge.png differ diff --git a/src/contrib/ccad/doc/html/_images/select_face.png b/src/contrib/ccad/doc/html/_images/select_face.png new file mode 100644 index 000000000..0c8d174b0 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/select_face.png differ diff --git a/src/contrib/ccad/doc/html/_images/select_vertex.png b/src/contrib/ccad/doc/html/_images/select_vertex.png new file mode 100644 index 000000000..cbdc22965 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/select_vertex.png differ diff --git a/src/contrib/ccad/doc/html/_images/solid_box.png b/src/contrib/ccad/doc/html/_images/solid_box.png new file mode 100644 index 000000000..fe081e641 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/solid_box.png differ diff --git a/src/contrib/ccad/doc/html/_images/solid_cone.png b/src/contrib/ccad/doc/html/_images/solid_cone.png new file mode 100644 index 000000000..7916be497 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/solid_cone.png differ diff --git a/src/contrib/ccad/doc/html/_images/solid_cylinder.png b/src/contrib/ccad/doc/html/_images/solid_cylinder.png new file mode 100644 index 000000000..ad87267ef Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/solid_cylinder.png differ diff --git a/src/contrib/ccad/doc/html/_images/solid_sphere.png b/src/contrib/ccad/doc/html/_images/solid_sphere.png new file mode 100644 index 000000000..ba06f7e45 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/solid_sphere.png differ diff --git a/src/contrib/ccad/doc/html/_images/solid_torus.png b/src/contrib/ccad/doc/html/_images/solid_torus.png new file mode 100644 index 000000000..e919f0f06 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/solid_torus.png differ diff --git a/src/contrib/ccad/doc/html/_images/solid_wedge.png b/src/contrib/ccad/doc/html/_images/solid_wedge.png new file mode 100644 index 000000000..3eccca6be Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/solid_wedge.png differ diff --git a/src/contrib/ccad/doc/html/_images/sphere_example.png b/src/contrib/ccad/doc/html/_images/sphere_example.png new file mode 100644 index 000000000..21c4c6e83 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/sphere_example.png differ diff --git a/src/contrib/ccad/doc/html/_images/trms_mirror.png b/src/contrib/ccad/doc/html/_images/trms_mirror.png new file mode 100644 index 000000000..176d483f8 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/trms_mirror.png differ diff --git a/src/contrib/ccad/doc/html/_images/trms_rotate.png b/src/contrib/ccad/doc/html/_images/trms_rotate.png new file mode 100644 index 000000000..96ab86fd6 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/trms_rotate.png differ diff --git a/src/contrib/ccad/doc/html/_images/trms_scale.png b/src/contrib/ccad/doc/html/_images/trms_scale.png new file mode 100644 index 000000000..d1ebb8fd2 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/trms_scale.png differ diff --git a/src/contrib/ccad/doc/html/_images/trms_translate.png b/src/contrib/ccad/doc/html/_images/trms_translate.png new file mode 100644 index 000000000..0eec70853 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/trms_translate.png differ diff --git a/src/contrib/ccad/doc/html/_images/wire.png b/src/contrib/ccad/doc/html/_images/wire.png new file mode 100644 index 000000000..3157df08e Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/wire.png differ diff --git a/src/contrib/ccad/doc/html/_images/wire_helix.png b/src/contrib/ccad/doc/html/_images/wire_helix.png new file mode 100644 index 000000000..4afa49dbc Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/wire_helix.png differ diff --git a/src/contrib/ccad/doc/html/_images/wire_ngon.png b/src/contrib/ccad/doc/html/_images/wire_ngon.png new file mode 100644 index 000000000..60b91efe0 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/wire_ngon.png differ diff --git a/src/contrib/ccad/doc/html/_images/wire_polygon.png b/src/contrib/ccad/doc/html/_images/wire_polygon.png new file mode 100644 index 000000000..349a19edd Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/wire_polygon.png differ diff --git a/src/contrib/ccad/doc/html/_images/wire_rectangle.png b/src/contrib/ccad/doc/html/_images/wire_rectangle.png new file mode 100644 index 000000000..5b6c6aae2 Binary files /dev/null and b/src/contrib/ccad/doc/html/_images/wire_rectangle.png differ diff --git a/src/contrib/ccad/doc/html/_sources/boolean.txt b/src/contrib/ccad/doc/html/_sources/boolean.txt new file mode 100644 index 000000000..abec3178e --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/boolean.txt @@ -0,0 +1,97 @@ +Boolean Operations +================== + +Boolean operations operate on two solids. The following shapes are +used in the following examples:: + + s1 = ccad.sphere(1.0) + s2 = ccad.box(1.0, 2.0, 3.0) + +**s3** is the derived shape. + +Fuse +---- + +fuse fuses two solids together. Either one of the two lines below +fuses **s1** and **s2**:: + + s3 = s1 + s2 + +or:: + + s3 = ccad.fuse(s1, s2) + +.. image:: boolean_fuse.png + +Cut +--- + +cut cuts one solid from the other. Either one of the two lines below +cuts **s2** from **s1**:: + + s3 = s1 - s2 + +or:: + + s3 = ccad.cut(s1, s2) + +.. image:: boolean_cut.png + +Common +------ + +common finds the common portions of each solid. Either one of the two +lines below finds the common portions of **s1** and **s2**:: + + s3 = s1 & s2 + +or:: + + s3 = ccad.common(s1, s2) + +.. image:: boolean_common.png + +Glue +---- + +Boolean operations can have trouble when two solids have coincident +faces. Sometimes the glue function can help. glue merges two solids +at a coincident face. Unfortunately, you need to know the face +indices that must merge. Read more about finding face indices in the +Display or Logging sections:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.box(1.0, 1.0, 1.0) + s2.translate((1.0, 0.5, 1.0)) + s3 = ccad.glue(s1, s2, [(1, 0)]) + +.. image:: boolean_glue.png + +A more robust glue called **simple_glue** exists in the API, which +isn't part of OCC. For this form, however, the faces to be glued must +exactly overlap. + +Notes +----- + +Functions to use OCC's old fuse, cut, and common algorithms, and +functions to perform a fillet or chamfer on the newly created edges +after a boolean operation are also available. I use them rarely. See +the API. + +Sometimes OCC has trouble with booleans. I found the following helpful: + + - Eliminate unneeded edges. OCC's boolean operations often return + two faces in the same domain with an edge between them that can be + merged. Eliminating these edges by merging the faces help + subsequent boolean operations. The *simplify* method can do this + for some shapes. + + - Move problem edges out of the way. Cylinder and sphere edges are + necessary in OCC, but their position can often be rotated away + from a problem boolean location. + + - Slice the object (with a box or something) into sections. Then + fuse those sections back together. Then apply the boolean + operation. This was particularly helpful for creating pockets in + a solid. diff --git a/src/contrib/ccad/doc/html/_sources/configuring_display.txt b/src/contrib/ccad/doc/html/_sources/configuring_display.txt new file mode 100644 index 000000000..e9e67b15e --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/configuring_display.txt @@ -0,0 +1,31 @@ +Configuring Display +=================== + +The viewer has a few simple configuring options to customize it for +your application. + +add_menuitem +------------ + +You may add another pulldown menu item with **add_menuitem**. The +following example adds the item *Car1* under the *Model* menu:: + + v1.add_menuitem(('Model', 'Car1'), display_car, 1) + v1.add_menuitem(('Model', 'Car2'), display_car, 2) + +The first argument is a tuple that defines the menu hierarchy. The +first item in the tuple is the pull-down menu title. The last item is +the menu item. Intermediate items are submenus. If a menu doesn't +exist, it is created. **display_car** is the function to call when the +item is selected. Pass parameters follow the function call. +**display_car** might look like this:: + + def display_car(widget, version): + ... + +add_menu +-------- + +If you know gtk and you want menu items fancier than strings, +**add_menu**, a lower-level menu manipulation method is available. See +the API for details. diff --git a/src/contrib/ccad/doc/html/_sources/contents.txt b/src/contrib/ccad/doc/html/_sources/contents.txt new file mode 100644 index 000000000..3f0ab70d6 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/contents.txt @@ -0,0 +1,11 @@ +Contents +======== + +.. toctree:: + :maxdepth: 2 + + intro + details + modelling + displaying + example1 diff --git a/src/contrib/ccad/doc/html/_sources/controlling_display.txt b/src/contrib/ccad/doc/html/_sources/controlling_display.txt new file mode 100644 index 000000000..331a62d9c --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/controlling_display.txt @@ -0,0 +1,117 @@ +Controlling Display +=================== + +The viewer has a number of methods that help control the display from +within your programs. + +viewstandard +------------ + +The viewing projection can be set to a standard projection with +viewstandard. The following example sets the view to front:: + + v1.viewstandard(viewtype = 'front') + +set_projection +-------------- + +The viewing projection can be set to any arbitrary projection with +set_projection. To look at the point (0.0, 0.0, 0.0) from the z-direction with the y-direction up, I could call:: + + v1.set_projection((0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, 1.0, 0.0)) + +set_scale +--------- + +set_scale scales the viewing window so the entire screen spans a +certain number of scene units. The following example sets the scale +such that a shape 8.0 long would span the screen:: + + v1.set_scale(8.0) + +set_size +-------- + +set_size sets the size of the viewing window in pixels. The following example sets the viewing window to be 600 by 800 pixels:: + + v1.set_size((600, 800)) + +set_background +-------------- + +set_background sets the background color. The following example sets +the background color to white:: + + v1.set_background((1.0, 1.0, 1.0)) + +set_foreground +-------------- + +set_foreground sets the default shape color. The following example +sets the foreground color to red:: + + v1.set_foreground((1.0, 0.0, 0.0)) + +set_triedron +------------ + +set_triedron sets how the triedron (the little x, y, z coordinate +sprite) is displayed. + +display +------- + +The examples have already used display extensively. However, there +are options to control color, material, transparency, line type, and +line width. The following example draws s1 red, in a metallized +material, halfway transparent:: + + s1 = ccad.sphere(1.0) + ccad.box(1.0, 2.0, 2.0) + v1.display(s1, color = (1.0, 0.0, 0.0), material = 'metallized', transparency = 0.5) + +.. image:: display_display.png + +fit +--- + +fits the scene to the window. + +clear +----- + +clear clears the window. + +save +---- + +save saves a screenshot. The following example saves the screen to +the file, 'screendump.png':: + + v1.save('screendump.png') + +mode_wireframe +-------------- + +mode_wireframe sets the window to wireframe display. + +mode_shaded +----------- + +mode_shaded sets the window to shaded display. + +mode_hlr +-------- + +mode_hlr sets the window to hidden line removal display. + +mode_drawing +------------ + +mode_drawing sets the window to a drafting-like drawing display by +using a custom hidden line removal mode. After calling it, call the +*reset_mode_drawing* method to restore regular control. + +quit +---- + +quit closes the window. diff --git a/src/contrib/ccad/doc/html/_sources/creating_derived.txt b/src/contrib/ccad/doc/html/_sources/creating_derived.txt new file mode 100644 index 000000000..880ccda16 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/creating_derived.txt @@ -0,0 +1,135 @@ +Creating Derived Shapes +======================= + +Some shapes are derived from others and create arbitrary shapes, +depending on what's passed to them. They follow. + +prism +----- + +A prism extrudes a shape in a given linear direction. A vertex is +extruded as an edge; an edge is extruded as a face; a wire is extruded +as a shell; and a face is extruded as a solid. The following example +creates an extrusion of a hexagon:: + + f1 = ccad.plane(ccad.ngon(2.0, 6)) + s1 = ccad.prism(f1, (0.0, 0.0, 1.0)) + +.. image:: derived_prism.png + +revol +----- + +A revol extrudes a shape in a circle. A vertex is extruded as an +edge; an edge is extruded as a face; a wire is extruded as a shell; +and a face is extruded as a solid. The following example extrudes a +squared circle about (0.0, 0.0, 0.0):: + + e1 = ccad.arc(1.0, -math.pi/2, math.pi/2) + e1.translate((3.0, 0.0, 0.0)) + w1 = ccad.polygon([(3.0, 1.0, 0.0), + (2.0, 1.0, 0.0), + (2.0, -1.0, 0.0), + (3.0, -1.0, 0.0)]) + f1 = ccad.plane(ccad.wire([e1, w1])) + s1 = ccad.revol(f1, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), 2*math.pi) + +.. image:: derived_revol.png + +loft +---- + +A loft returns a solid or shell that fits a list of closed wires. The +following example fits a changing-diameter circle:: + + w1 = ccad.wire([ccad.circle(1.0)]) + w2 = ccad.wire([ccad.circle(2.0)]) + w2.translate((0.0, 0.0, 1.0)) + w3 = ccad.wire([ccad.circle(1.5)]) + w3.translate((0.0, 0.0, 2.0)) + s1 = ccad.loft([w1, w2, w3], stype = 'solid') + +.. image:: derived_loft.png + +pipe +---- + +A pipe returns a solid or shell that is an extrusion of a closed wire +profile along a wire spine. The following example extrudes a hexagon +along an arching and then straight spine:: + + profile = ccad.ngon(2.0, 6) + e1 = ccad.arc(8.0, 0.0, math.pi/2) + e2 = ccad.segment((0.0, 8.0, 0.0), (-8.0, 8.0, 0.0)) + spine = ccad.wire([e1, e2]) + spine.translate((-8.0, 0.0, 0.0)) + spine.rotatex(math.pi/2) + s1 = ccad.pipe(profile, spine) + +.. image:: derived_pipe.png + +helical_solid +------------- + +A helical_solid is a pipe spun on a helical spine. Unfortunately, +pipe had trouble with the profile's orientation on a helix, so +helical_solid was needed. The following example extrudes a triangle +along a helix:: + + profile = ccad.ngon(0.2, 3) + s1 = ccad.helical_solid(profile, 2.0, 1.0/math.pi, 2) + +.. image:: derived_helical_solid.png + +offset +------ + +offset offsets a face or solid by a given distance and returns a list +of faces or solids. Why doesn't it return a single face or solid? +Depending on the distance, offsetting can create multiple shapes from +one. For example, consider a 2D version of an octopus. As offset +gets larger and larger, parts of the face merge and can create holes. +The following example offsets a hexagon by 1.0. In this example, the +returned list is only one item long:: + + w1 = ccad.ngon(8.0, 6) + w2 = ccad.offset(ccad.plane(w1), 1.0)[0] + +.. image:: derived_offset_face.png + +The following example offsets a box with a hole by 1.0. In this +example, the returned list is only one item long:: + + b1 = ccad.box(10.0, 10.0, 10.0) + b1.translate((-5.0, -5.0, 0.0)) + c1 = ccad.cylinder(2.5, 20.0) + c1.translate((0.0, 0.0, -5.0)) + s1 = b1 - c1 + s2 = ccad.offset(s1, 1.0)[0] + +.. image:: derived_offset_solid.png + +Note the offset shape has edges rounded. Currently, this is the only +option OCC provides. + +A positive value for offset makes the new face or solid outside the +original. A negative value makes the new face or solid inside the +original. + +slice +----- + +slice slices a solid with a plane and returns a list of faces at the +intersection of the solid and plane. The plane can be passed or a +value for x, y, or z can be given which generates the plane. The +following example slices a box with a hole at z = 1.0:: + + b1 = ccad.box(10.0, 10.0, 10.0) + b1.translate((-5.0, -5.0, 0.0)) + c1 = ccad.cylinder(2.5, 20.0) + c1.translate((0.0, 0.0, -5.0)) + s1 = b1 - c1 + f1 = ccad.slice(s1, z = 1.0)[0] + +.. image:: derived_slice.png + diff --git a/src/contrib/ccad/doc/html/_sources/creating_edges.txt b/src/contrib/ccad/doc/html/_sources/creating_edges.txt new file mode 100644 index 000000000..780a1c994 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/creating_edges.txt @@ -0,0 +1,90 @@ +Creating Edges +============== + +Functions that instantiate an edge follow. In each example, the edge +is called **e1**. + +segment +------- + +A segment defines a straight line from one point to another. The +following example creates a segment from the origin to the point +(1.0, 0.0, 0.0):: + + pt1 = (0.0, 0.0, 0.0) + pt2 = (1.0, 0.0, 0.0) + e1 = ccad.segment(pt1, pt2) + +.. image:: edge_segment.png + +arc +--- + +An arc defines a portion of a circle. The following example creates +an arc centered on the origin of radius 1.0 that extends from 0.0 +radians to pi/2 radians:: + + e1 = ccad.arc(1.0, 0.0, math.pi/2) + +.. image:: edge_arc.png + +arc_ellipse +----------- + +An arc_ellipse defines a portion of an ellipse. The following example +creates an arc_ellipse centered on the origin with major radius 2.0, +minor radius 1.0, and that extends from 0.0 radians to pi/2 radians:: + + e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2) + +.. image:: edge_arc_ellipse.png + +spline +------ + +A spline defines a 3D spline fit to a series of passed points. The +following example creates a spline that passes through the points +(0.0, 0.0, 0.0), (0.2, 0.1, 0.0), (0.5, 0.2, 0.0), (-0.5, 0.3, 0.0):: + + e1 = ccad.spline([(0.0, 0.0, 0.0), + (0.2, 0.1, 0.0), + (0.5, 0.2, 0.0), + (-0.5, 0.3, 0.0)]) + +.. image:: edge_spline.png + +bezier +------ + +A bezier edge is an edge that passes from and to the endpoints. The +intermediate points shape the curve. The following example creates a +bezier that is the same as a quarter circle:: + + e1 = ccad.bezier([(1.0, 0.0, 0.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 0.0)], [1.0, 1.0/math.sqrt(2.0), 1.0]) + +.. image:: edge_bezier.png + +It can also be called without weights, which will return the curves +used in svg files. + +circle +------ + +The following example creates a circle centered on the origin with +radius 3.0:: + + e1 = ccad.circle(3.0) + +.. image:: edge_circle.png + +ellipse +------- + +The following example creates an ellipse centered on the origin with major radius 2.0 and minor radius 1.0:: + + e1 = ccad.ellipse(2.0, 1.0) + +.. image:: edge_ellipse.png + diff --git a/src/contrib/ccad/doc/html/_sources/creating_faces.txt b/src/contrib/ccad/doc/html/_sources/creating_faces.txt new file mode 100644 index 000000000..0b2428e12 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/creating_faces.txt @@ -0,0 +1,52 @@ +Creating Faces +============== + +Functions that instantiate a face follow. In each example, the face +is called **f1**. + +plane +----- + +A plane is a flat shape bounded by a closed wire in 3D space. The +wire must be planar (flat) to work. The following example creates a +face inside an ngon:: + + w1 = ccad.ngon(2.0, 5) + f1 = ccad.plane(w1) + +.. image:: face_plane.png + +face_from +--------- + +face_from creates a face that mathematically exactly matches another +face but is bounded by a different wire. The following example +creates a filled square from a filled octagon. The example isn't very +practical. face_from is more often used when the face is 3D and you +want to bound it by a different wire:: + + w1 = ccad.ngon(2.0, 8) + w2 = ccad.ngon(10.0, 4) + f2 = ccad.plane(w1) + f1 = ccad.face_from(f2, w2) + +.. image:: face_face_from.png + +filling +------- + +A filling is a surface fit to a wire, and that wire does not need to +be planar. The following example fits a surface to a wavy wire. Both +the wire and face are shown:: + + w1 = ccad.spline([(0.0, 0.0, 0.0), + (1.0, 0.2, 0.3), + (1.5, 0.8, 1.0), + (0.8, 1.2, 0.2), + (-0.5, 0.8, -0.5), + (0.0, 0.0, 0.0)]) + f1 = ccad.filling(w1) + +.. image:: face_filling.png + +Visit the API documentation for more filling options. diff --git a/src/contrib/ccad/doc/html/_sources/creating_solids.txt b/src/contrib/ccad/doc/html/_sources/creating_solids.txt new file mode 100644 index 000000000..e86edea3d --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/creating_solids.txt @@ -0,0 +1,75 @@ +Creating Solids +=============== + +Functions that instantiate a solid follow. In each example, the solid +is called **s1**. + +box +--- + +A box is a six-sided perpendicular-faced solid. The following example +creates a box that extends 1.0 in the x-direction, 2.0 in the +y-direction, and 3.0 in the z-direction:: + + s1 = ccad.box(1.0, 2.0, 3.0) + +.. image:: solid_box.png + +wedge +----- + +A wedge is a box with one or more tapered sides. The following +example creates a wedge that extends 2.0 in the y-direction and 3.0 in +the z-direction and tapers from 1.0 to 0.5 in the x-direction:: + + s1 = ccad.wedge(1.0, 2.0, 3.0, 0.5) + +.. image:: solid_wedge.png + +cylinder +-------- + +The following example creates a cylinder 1.0 in radius and 2.0 high:: + + s1 = ccad.cylinder(1.0, 2.0) + +.. image:: solid_cylinder.png + +sphere +------ + +The following example creates a sphere 5.0 in radius:: + + s1 = ccad.sphere(5.0) + +.. image:: solid_sphere.png + +cone +---- + +The following example creates the bottom of a cone, radius 4.0 at the +bottom, 2.0 at the top, and 2.0 high:: + + s1 = ccad.cone(4.0, 2.0, 2.0) + +.. image:: solid_cone.png + +A similar function, bezier_cone, returns an identical solid, but the +cone face is a surface of revolution. Fillets worked better with it +sometimes. + +torus +----- + +The following example creates a torus 10.0 from torus center to +extruded circle center, and 1.0 in radius:: + + s1 = ccad.torus(10.0, 1.0) + +.. image:: solid_torus.png + +Notes +----- + +Many of these functions offer more options than shown. Consult the +API for details. diff --git a/src/contrib/ccad/doc/html/_sources/creating_wires.txt b/src/contrib/ccad/doc/html/_sources/creating_wires.txt new file mode 100644 index 000000000..0cc768096 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/creating_wires.txt @@ -0,0 +1,71 @@ +Creating Wires +============== + +Functions that instantiate a wire follow. In each example, the wire +is called **w1**. + +Connection of Edges +------------------- + +wire is one of the few classes you'll instantiate directly in ccad. +Often, you'll create a wire manually by connecting multiple edges. Do +it by passing a list of edges to a wire instantiation. The following +example creates an elongated circle:: + + e1 = ccad.arc(1.0, -math.pi/2, math.pi/2) + e1.translate((1.0, 0.0, 0.0)) + e2 = ccad.segment((1.0, 1.0, 0.0), (-1.0, 1.0, 0.0)) + e3 = ccad.arc(1.0, math.pi/2, 3*math.pi/2) + e3.translate((-1.0, 0.0, 0.0)) + e4 = ccad.segment((-1.0, -1.0, 0.0), (1.0, -1.0, 0.0)) + w1 = cm.wire([e1, e2, e3, e4]) + +.. image:: wire.png + +You'll learn more about *translate* later. + +polygon +------- + +A polygon defines a connection of segments. To close the polygon, +make the last point equal to the first. The following example creates +a polygon:: + + w1 = ccad.polygon([(0.0, 0.0, 0.0), + (1.0, 0.0, 0.0), + (1.5, 1.0, 0.0), + (0.5, 1.5, 0.0), + (-0.5, -0.5, 0.0)]) + +.. image:: wire_polygon.png + +rectangle +--------- + +Create a rectangle by passing the x-distance and y-distance. The following example creates a rectangle 2.0 wide and 1.0 high. The lower-left corner is at (0.0, 0.0, 0.0):: + + w1 = ccad.rectangle(2.0, 1.0) + +.. image:: wire_rectangle.png + +ngon +---- + +An ngon defines an equal-sided polygon with n segments. The following +example creates a hexagon with radius (distance from center to vertex) +of 2.0:: + + w1 = ccad.ngon(2.0, 6) + +.. image:: wire_ngon.png + +helix +----- + +The following example creates a helix with radius 2.0, helical angle +of 1.0/pi, and 3 turns:: + + w1 = ccad.helix(2.0, 1.0/math.pi, 3) + +.. image:: wire_helix.png + diff --git a/src/contrib/ccad/doc/html/_sources/details.txt b/src/contrib/ccad/doc/html/_sources/details.txt new file mode 100644 index 000000000..d2fb21f41 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/details.txt @@ -0,0 +1,58 @@ +Details +======= + +Internals +--------- + +ccad is a python wrapper of pythonocc, which is a python wrapper of +OpenCascade, an open source mechanical CAD math engine. So why wrap +python with more python? pythonocc is a SWIG wrapper of a C++ +project. Because of that, the syntax is fairly cumbersome. The +sphere instance from the introduction page looks like this in +pythonocc:: + + from OCC.BRepPrimAPI import * + s1 = BRepPrimAPI_Sphere(10.0).Shape() + +ccad syntax is simpler, and allows you to focus more on design and +less on syntax. Put simply, pythonocc is more for CAD developers; +ccad is more for CAD users. + +Unfortunately, ccad use comes with a cost. Not all of pythonocc's +abilities are yet wrapped. Therefore, it lacks the power of +pythonocc. + +However, extending ccad isn't too hard. You can extend ccad with +python calls to pythoncc. No C or C++ coding is necessary. We're +always looking for pythonocc-skilled people to extend ccad's +abilities. + +User Requirements +----------------- + +You'll need to know python reasonably well to use ccad. Additionally, +the *pydoc* generated documentation adds further detail to this +manual. Keep it handly. + +System Requirements +------------------- + +You'll need pythonocc, python-gtk, and python-gtkglext to run ccad. + +Installation +------------ + +To install ccad, follow the following procedure in Linux:: + + tar xvzf ccad-ver.tar.gz (where ver is the version number) + cd ccad-ver (where ver is the version number) + python setup.py install --prefix=/usr/local (as root) + +Change the prefix argument to install in a different directory. + +If you're a Windows or Mac user, ccad should still work. I just +haven't tried it. If you successfully install ccad in Windows or Mac, +let us know how you did it, and we'll update the documentation. + +If you're having trouble, simply extract the .tar.gz file. Then, add +that directory to your PYTHONPATH. That, at least, will get you going. diff --git a/src/contrib/ccad/doc/html/_sources/displaying.txt b/src/contrib/ccad/doc/html/_sources/displaying.txt new file mode 100644 index 000000000..7563df919 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/displaying.txt @@ -0,0 +1,15 @@ +Displaying +========== + +You have your model in text. Now you want to see it. The ccad viewer +can be run stand-alone or interactively. + +.. toctree:: + :maxdepth: 2 + + stand_alone + interactive + using_display + controlling_display + configuring_display + diff --git a/src/contrib/ccad/doc/html/_sources/example1.txt b/src/contrib/ccad/doc/html/_sources/example1.txt new file mode 100644 index 000000000..8c6083204 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/example1.txt @@ -0,0 +1,30 @@ +Example - Building Toy +====================== + +Suppose I want to generate a brick building toy for plastic injection +molding, similar to the one we know and love. I'm looking for bricks +with the following unit configurations all the same height. + +- 1 x 1 +- 2 x 1 +- 4 x 1 +- 6 x 1 +- 8 x 1 +- 2 x 2 +- 4 x 2 +- 8 x 2 +- 4 x 4 + +10mm is the brick height, and a unit brick will be 5mm by 5mm in x and +y. I'll use ipython to develop and modify. When I'm pleased with a +block of code, I'll place it in a file for future use. I won't show +the ipython prompt. + +.. toctree:: + :maxdepth: 2 + + example1_singlebrick + example1_brickpy + example1_multibrick + example1_drawings + example1_nuances diff --git a/src/contrib/ccad/doc/html/_sources/example1_brickpy.txt b/src/contrib/ccad/doc/html/_sources/example1_brickpy.txt new file mode 100644 index 000000000..875d68d48 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/example1_brickpy.txt @@ -0,0 +1,10 @@ +brick.py +======== + +Now that I'm satisfied with my brick, I'll place the ipython code in a +function in **brick.py**. I'll call the function **brick** and pass **xsize** +and **ysize** as parameters. I'll also make the fillets an option. +**brick.py** now looks like this: + +.. include:: brick.py + :literal: diff --git a/src/contrib/ccad/doc/html/_sources/example1_drawings.txt b/src/contrib/ccad/doc/html/_sources/example1_drawings.txt new file mode 100644 index 000000000..7ffb004e6 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/example1_drawings.txt @@ -0,0 +1,90 @@ +Drawings +======== + +OCC has a number of capabilities to turn a model into engineering +drawings with dimensions and annotations. ccad does not yet implement +them, but the hidden line removal features are enough to generate some +simple patent drawings. + +Requirements +------------ + +I'm pleased with my brick, and I'd like to create some patent drawings +from it. I'd like top, side, isometric, and bottom views. In case I +change my brick, I'd like the drawings to auto-generate. I'll use the +*4x2* example. + +brick_images.py +--------------- + +I'll create a file called **brick_images.py**. It starts like this:: + + import ccad + import brick + + def generate_images(xsize, ysize): + pass + + if __name__ == '__main__': + generate_images() + +Currently, it does nothing. I want to write the **generate_images** +function. First, I'll create the projection vectors for the various +views:: + + unit = 5.0 + height = 10.0 + center = (unit*xsize/2, unit*ysize/2, height/2) + projections = {'top': (center, (0.0, 0.0, 1.0), (0.0, 1.0, 0.0)), + 'side': (center, (1.0, 0.0, 0.0), (0.0, 0.0, 1.0)), + 'iso': (center, + (math.sqrt(0.4), -math.sqrt(0.4), math.sqrt(0.2)), + (-math.sqrt(0.5), math.sqrt(0.5), 0.0)), + 'bottom': (center, + (math.sqrt(0.4), -math.sqrt(0.4), -math.sqrt(0.2)), + (-math.sqrt(0.5), math.sqrt(0.5), 0.0))} + +Now, I'll create a brick and view instance:: + + b1 = brick.brick(xsize, ysize, fillet = False) + v1 = ccad.view() + +Note that I called brick with fillet set to *False*. Smooth edges +(e.g. filleted ones) are not shown in *drawing* mode. We need them +sharp to see them. + +Now, I'll fix the background, scale, and size of the view:: + + v1.set_background((1.0, 1.0, 1.0)) + v1.set_scale(24.0) + v1.set_size((600, 400)) + +Finally, I'll iterate over each projection, display it in black lines, +and save it:: + + for projection in projections: + vcenter, vout, vup = projections[projection] + v1.clear() + v1.set_projection(vcenter, vout, vup) + v1.display(b1, color = (0.0, 0.0, 0.0), line_width = 2.0) + v1.mode_drawing() + v1.save(name = 'brick_' + projection + '.png') + +Note the **mode_drawing** call after **display**. This redraws the image +in a drafting-like mode. + +Now, I'll call **generate_images** on start:: + + if __name__ == '__main__': + generate_images(4, 2) + +When I run the program, it generates the following images: + +.. image:: brick_top.png + +.. image:: brick_side.png + +.. image:: brick_iso.png + +.. image:: brick_bottom.png + diff --git a/src/contrib/ccad/doc/html/_sources/example1_multibrick.txt b/src/contrib/ccad/doc/html/_sources/example1_multibrick.txt new file mode 100644 index 000000000..9e3d0be40 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/example1_multibrick.txt @@ -0,0 +1,50 @@ +Multiple Bricks +=============== + +Because ccad is python-programming, it allows huge power in design +reuse. Our building toy serves as a great example. + +I'll create a new file and call it **all_bricks.py**. First, I'll +define the various brick sizes I want in the toy:: + + import ccad + import brick + + sizes = [(1, 1), + (2, 1), + (4, 1), + (6, 1), + (8, 1), + (2, 2), + (2, 4), + (2, 8), + (4, 4)] + +Now, I'll define a display function to view a given brick:: + + def display_brick(widget, view, size): + view.clear() + view.display(brick.brick(size[0], size[1])) + +Finally, I'll write the code to run at startup:: + + if __name__ == '__main__': + v1 = ccad.view() + for size in sizes: + name = 'brick' + str(size[0]) + 'x' + str(size[1]) + v1.add_menuitem(('Bricks', name), display_brick, v1, size) + ccad.start() + +The startup code instantiates a view, adds a menu item for every brick +in **sizes** and starts the viewer in stand-alone mode. + +The figure below shows the **all_bricks.py** window with the *8x1* brick +displayed: + +.. image:: example1_all_bricks.png + +Try to appreciate the power. With a few more lines, I can generate 9 +different bricks from single brick code, and because it's python, the +complexity is nearly unlimited. By contrast, complicated model reuse +can be difficult-to-impossible in GUI-based CAD systems. + diff --git a/src/contrib/ccad/doc/html/_sources/example1_nuances.txt b/src/contrib/ccad/doc/html/_sources/example1_nuances.txt new file mode 100644 index 000000000..36300f2aa --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/example1_nuances.txt @@ -0,0 +1,9 @@ +Nuances +======= + +While the building toy example appears to be a step-by-step smooth +process, in reality, it wasn't. OCC often has trouble with fillets +and boolean operations. I had to play with the model and fillets +before I had something which worked robustly. I originally included +those trails-to-nowhere to help you see how they can be overcome, but +it became over-complicated. I removed them, and it looks simple now. diff --git a/src/contrib/ccad/doc/html/_sources/example1_singlebrick.txt b/src/contrib/ccad/doc/html/_sources/example1_singlebrick.txt new file mode 100644 index 000000000..7e36fe5a0 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/example1_singlebrick.txt @@ -0,0 +1,228 @@ +A Single Brick +-------------- + +First, I'll start by importing the required packages:: + + import math + import ccad + +Now, I'll define some dimensions for all bricks:: + + unit = 5.0 + height = 10.0 + knob_rad = 1.8 # radius of the brick knob for mating with other bricks + knob_draft = 5.0 # degrees of draft for the knob + knob_height = 2.0 + wall_thickness = 1.6 # plastic wall thickness + draft = 1.0 # degrees of draft on faces for plastic ejection + fillet_rad = 0.4 # the default radius to use for rounded edges + +In ipython, I set up the specific brick's dimensions. Later, these +will be pass parameters in a function:: + + xsize = 4 + ysize = 2 + +Now, I begin making the outer shape. I could use **box**, but most +injection molders want you to draft the walls to make machining and +ejection easier. **loft* tends to be more robust than **draft** on +arbitrary shapes, so I'll use that:: + + dx = height * math.tan(math.radians(draft)) + wbottom = ccad.rectangle(unit * xsize, unit * ysize) + wtop = ccad.rectangle(unit * xsize - 2*dx, unit * ysize - 2*dx) + wtop.translate((dx, dx, height)) + brick = ccad.loft([wbottom, wtop], True) + + view1 = ccad.view() + view1.display(brick) + +.. image:: example1_box.png + +Here, the brick bottom is defined by the wire, **wbottom**, and the +brick top is defined by the wire, **wtop**. The **loft** with ruled set +to *True* converts the wires to a ruled solid: + +I need a knob on top for mating with bricks above, and I want that +knob to aid in alignment by tapering it. A cone would do nicely:: + + drad = knob_height * math.tan(math.radians(knob_draft)) + knob_top_rad = knob_rad - drad + knob_base_rad = knob_rad + drad + knob = ccad.cone(knob_base_rad, knob_top_rad, 2 * knob_height) + knob.translate((0.5 * unit, 0.5 * unit, height - knob_height)) + +Notice the knob is higher than it needed to be, then offset. Usually, +boolean operations are less buggy when surfaces don't coincide on +their faces. That is, make the knob penetrate into the brick for more +robust boolean operations. + +Now, I add the knob to the view to make sure I like the position. +It's added in red:: + + view1.display(knob, color = (1.0, 0.0, 0.0)) + +.. image:: example1_boxwknob.png + +Now, I'll replicate the knob and fuse it with the brick:: + + for x in range(xsize): + for y in range(ysize): + brick = brick + ccad.translated(knob, (x * unit, y * unit, 0.0)) + + view1.clear() + view1.display(brick) + +.. image:: example1_boxwknobs.png + +Note the use of the *function* form of translate here: +**ccad.translated** versus the *method* form for wtop: **wtop.translate**. +I use the function form when I want to *copy* a shape. I use the +method form when I want to *change* a shape. + +Injection molders like same-thickness walls, so I need to core-out the +brick. At this point, it looks like most of my code can be replicated +for the core, so I'll place the solid brick in its own function and +add a **wall_offset** to its pass parameters and place it in a file +called **brick.py**:: + + import ccad + import math + + def solidbrick(xsize, ysize, wall_offset, unit, height, draft, + knob_rad, knob_height, knob_draft): + + dx = height * math.tan(math.radians(draft)) + wbottom = ccad.rectangle(unit*xsize - 2*wall_offset, + unit*ysize - 2*wall_offset) + wtop = ccad.rectangle(unit*xsize - 2*dx - 2*wall_offset, + unit*ysize - 2*dx - 2*wall_offset) + wtop.translate((dx, dx, height)) + brick = ccad.loft([wbottom, wtop], True) + brick.translate((wall_offset, wall_offset, 0.0)) + + drad = knob_height * math.tan(math.radians(knob_draft)) + knob_top_rad = knob_rad - drad + knob_base_rad = knob_rad + drad + knob = ccad.cone(knob_base_rad, knob_top_rad, 2 * knob_height) + knob.translate((0.5 * unit, 0.5 * unit, height - knob_height)) + + for x in range(xsize): + for y in range(ysize): + brick = brick + ccad.translated(knob, (x * unit, y * unit, 0.0)) + + return brick + +And, I can import it in ipython:: + + import brick + + outerbrick = brick.solidbrick(xsize, ysize, 0.0, unit, height, draft, + knob_rad, knob_height, knob_draft) + +**outerbrick** is now the outside of the brick. + +Before I create the inside of the brick, I ought to add fillets to +**outerbrick**. Many injection molders use milling to form the mold. +Milling requires fillets wherever the ball endmill can't make a sharp +corner. For the cavity, it's the convex edges. For the core, it's +the concave edges. To make things simple, I usually fillet every +edge. However, to show off some of ccad's features, I'll fillet only +the edges I must fillet. + +In outerbrick, I want to fillet the tip of every knob, the side walls, +and the top walls:: + + to_fillet = [] + for count, edge_center in enumerate(outerbrick.subcenters('edge')): + if (abs(edge_center[2]) < 0.1 or + (abs(edge_center[2] - height) < 0.1 and + abs(edge_center[0] - 0.5*unit) % unit < 0.1 and + abs(edge_center[1] - 0.5*unit) % unit < 0.1)): + pass + else: + to_fillet.append(count) + outerbrick.fillet(fillet_rad, to_fillet) + +The loop goes through every edge in **outerbrick**, analyzing the edge's +center. Edges whose z-component is near zero are not filleted. +Additionally, edges with z-component equal to **height** and +xy-components near the knob positions are not filleted. All other +edges are filleted. Here's what I get:: + + v.clear() + v.display(outerbrick) + +.. image:: example1_outerbrick.png + +With the **solidbrick** function, I can now define the inside of the brick:: + + innerbrick = brick.solidbrick(xsize, ysize, wall_thickness, + unit, height - wall_thickness, draft, + knob_rad - wall_thickness, knob_height, + knob_draft) + base = ccad.box(2*unit*xsize, 2*unit*ysize, 1.0) + base.translate((-0.5*unit*xsize, -0.5*unit*ysize, -1.0)) + innerbrick = innerbrick + base + + view1.clear() + view1.display(innerbrick) + +.. image:: example1_innerbrick.png + +Note, I have added a base to **innerbrick**. It will be helpful when I +perform the boolean cut. Finally, I'll fillet **innerbrick**:: + + to_fillet = [] + for count, edge_center in enumerate(innerbrick.subcenters('edge')): + if (abs(edge_center[2]) < 0.1 or + (abs(edge_center[2] - (height - wall_thickness)) < 0.1 and + abs(edge_center[0] - 0.5*unit) % unit < 0.1 and + abs(edge_center[1] - 0.5*unit) % unit < 0.1)): + to_fillet.append(count) + innerbrick.fillet(fillet_rad, to_fillet) + + v.clear() + v.display(innerbrick) + +.. image:: example1_innerbrickfillet.png + +You should recognize the fillet code. It's the opposite of the edges +filleted for **outerbrick**, because it's a core. + +Finally, I'll perform the cut:: + + brick = outerbrick - innerbrick + + v.clear() + v.set_projection((0.0, 0.0, 0.0), + (math.sqrt(0.45), -math.sqrt(0.1), -math.sqrt(0.45)), + (0.0, -1.0, 0.0)) + v.display(brick) + +.. image:: example1_brick.png + +I'm almost there. I now only need the posts that sit between the +knobs to add more holding force. Like the knobs, I'll make the posts +cones and fillet the tips:: + + post_rad = (math.sqrt(2.0)*unit - 2*knob_rad)/2.0 + drad = (height - 0.5*wall_thickness) * math.tan(math.radians(draft)) + post_base_rad = post_rad + drad + post = ccad.cone(post_rad, post_base_rad, height - 0.5*wall_thickness) + post.fillet(fillet_rad, [(0.0, 0.0, 0.0)]) + +.. image:: example1_post.png + +Here, I've chosen to pass a list of the center positions of the edges +to be filleted to the fillet method. In this case, I'm only filleting +one edge, the tip. + +Now, I translate it and add it to the brick:: + + post.translate((unit/2, unit/2, 0.0)) + for x in range(xsize - 1): + for y in range(ysize - 1): + brick = brick + ccad.translated(post, (x * unit, y * unit, 0.0)) + +.. image:: example1_brickpost.png diff --git a/src/contrib/ccad/doc/html/_sources/file_transfer.txt b/src/contrib/ccad/doc/html/_sources/file_transfer.txt new file mode 100644 index 000000000..7d8d59dea --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/file_transfer.txt @@ -0,0 +1,89 @@ +File Transfer +============= + +Sometimes, you'll want to save and restore data. Also, you'll want to +transfer designs with the outside world. ccad offers the following +features. + +to_brep / from_brep +------------------- + +All shapes offers a to_brep method to store a shape in OCC's native +format:: + + s1 = cm.sphere(1.0) + s1.to_brep('sphere.brep') + +ccad also offers the function from_brep to import a .brep file:: + + s1 = ccad.from_brep('sphere.brep') + +to_iges / from_iges +------------------- + +All shapes offer a to_iges method to store shapes in IGES format:: + + s1 = cm.sphere(1.0) + s1.to_iges('sphere.igs') + +ccad also offers the function from_iges to import an IGES file:: + + s1 = ccad.from_iges('sphere.igs') + +There is one slight nuance to this example: the shape type changed:: + + s1.stype + 'face' + +To fix, use the *brep_mode* pass parameter:: + + s1.to_iges('sphere.igs', brep_mode = 1) + s1 = ccad.from_iges('sphere.igs') + s1.stype + 'solid' + +to_step / from_step +------------------- + +All shapes offer a to_step method to store shapes in STEP format:: + + s1 = cm.sphere(1.0) + s1.to_step('sphere.stp') + +ccad also offers the function from_step to import a STEP file:: + + s1 = ccad.from_step('sphere.stp') + +to_stl +------ + +Solids offer a to_stl method to store solids in STL format:: + + s1 = cm.sphere(1.0) + s1.to_stl('sphere.stl') + +ccad does not offer a from_stl function. + +from_svg +-------- + +ccad offers a from_svg function to import a two dimensional SVG file. +It converts each svg path into a wire and returns a list of wires:: + + ws = ccad.from_svg('logo.svg') + for w in ws: + view1.display(w) + +The .svg file + +.. image:: logo.png + +ccad's conversion of it + +.. image:: from_svg.png + +Notes +----- + +Many of these methods and functions offer considerably more conversion +options than shown. Consult the API for all options. diff --git a/src/contrib/ccad/doc/html/_sources/interactive.txt b/src/contrib/ccad/doc/html/_sources/interactive.txt new file mode 100644 index 000000000..a387741c2 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/interactive.txt @@ -0,0 +1,52 @@ +Interactive Viewing +=================== + +Introduction +------------ + +To run the viewer in stand-alone mode, you need ipython. Start +ipython with:: + + ipython -gthread + +The ccad viewer uses gtk; that's why you use the -gthread option. + +Now, from the ipython prompt, you can work with ccad interactively. +Try this:: + + In [1]: import ccad + In [2]: s1 = ccad.sphere(1.0) + In [3]: s2 = ccad.box(1.0, 2.0, 3.0) + In [4]: s2.translate((2.0, 0.0, 0.0)) + In [5]: v1 = ccad.view() + +After the last line, you should see a window appear with nothing in +it. **v1** is an instance of a viewing window. Now type:: + + In [6]: v1.display(s1) + +You should see **s1** in your window. Move to the window, hold on the +middle mouse button and pan. You should see the shape moving. Now, +go back to the ipython prompt and type:: + + In [7]: v1.display(s2) + +You'll see **s2** appear in the viewer. + +To clear the window, use:: + + In [8]: v1.clear() + +You have interactive viewing. + +Now, start a second window with:: + + In [9]: v2 = ccad.view() + +**v2** is an instance of the second displayed window. Add something +different to it:: + + In [10]: v2.display(ccad.cone(4.0, 2.0, 2.0)) + +Note that each viewer is independent. You may create as many view +windows as you like. diff --git a/src/contrib/ccad/doc/html/_sources/intro.txt b/src/contrib/ccad/doc/html/_sources/intro.txt new file mode 100644 index 000000000..bc890135a --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/intro.txt @@ -0,0 +1,35 @@ +Introduction +============ + +ccad is a text-based mechanical CAD (computer aided design) tool. It +is a python module you import within a python file or from the python +prompt. Once imported, you can create and view mechanical objects. + +A simple example may help. From the ipython prompt, I can type the +following:: + + In [1]: import ccad + In [2]: s1 = ccad.sphere(2.0) + In [3]: v1 = ccad.view() + In [4]: v1.display(s1) + +After the third command, a window appears, and after the last command, +it looks like this: + +.. image:: sphere_example.png + +I've created and displayed a sphere of radius 2.0. Now, suppose I +want to 3D print this sphere. From the prompt, I can type:: + + In [5]: s1.to_stl('sphere.stl') + +And, I have my sphere in .stl format, ready to ship to a 3D printer. +ccad lets you do much more than creating a sphere. To learn more, +read on. + +This Document +------------- + +This document and the examples shown provide an overview of what's +available in ccad. Consult the API (easily available with pydoc) for +more options and better descriptions of the passed parameters. diff --git a/src/contrib/ccad/doc/html/_sources/logging.txt b/src/contrib/ccad/doc/html/_sources/logging.txt new file mode 100644 index 000000000..a9bbbdd7e --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/logging.txt @@ -0,0 +1,408 @@ +Gathering Information and Miscellaneous Methods +=============================================== + +Overview +-------- + +Shapes can report a variety of information about themselves. These +information gathering queries all operate as class methods. +Additionally, there are some methods that didn't fit the modelling +categories before. They're below. + +All classes support the following methods: + +copy +^^^^ + +All shapes offer a copy method. copy returns a copy of the shape. +The following example makes **s2** a new copy of **s1**:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = s1.copy() + +subshapes +^^^^^^^^^ + +subshapes reports the shapes of a specific type that make up a shape. +The following example sets **edges** to a list of the edges in **s1**:: + + s1 = ccad.box(1.0, 2.0, 3.0) + edges = s1.subshapes('edge') + +The passed parameter may be *vertex*, *edge*, *wire*, *face*, or *shell*. + +bounds +^^^^^^ + +bounds places an imaginary box around the shape and returns a 6-tuple +that describes the minimum and maximum (x, y, z) boundaries of the +box:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.bounds() + (-9.9999999999999995e-08, -9.9999999999999995e-08, -9.9999999999999995e-08, + 1.0000001000000001, 2.0000000999999998, 3.0000000999999998) + +center +^^^^^^ + +center returns the center of the shape:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.center() + (0.5, 1.0, 1.5) + +subcenters +^^^^^^^^^^ + +subcenters returns a list of the centers of the subshapes of a shape:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.subcenters('edge') + [(0.0, 0.0, 1.5), (0.0, 1.0, 3.0), (0.0, 2.0, 1.5), (0.0, 1.0, 0.0), + (1.0, 0.0, 1.5), (1.0, 1.0, 3.0), (1.0, 2.0, 1.5), (1.0, 1.0, 0.0), + (0.5, 0.0, 0.0), (0.5, 0.0, 3.0), (0.5, 2.0, 0.0), (0.5, 2.0, 3.0)] + +check +^^^^^ + +check performs an OCC BRep check on the shape to make sure there's +nothing wrong with it. It returns 1 if it's okay:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.check() + 1 + +fix +^^^ + +fix attempts to fix a faulty shape. It sometimes works and sometimes +doesn't. It's usually best to find out what caused the shape to be +corrupted in the first place and fix that. It does nothing if the +shape's okay:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.fix() + +nearest +^^^^^^^ + +nearest returns the index of the subshape whose center is closest to a +passed position. It operates on a list of positions:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.nearest('edge', [(0.0, 1.0, 3.0), (1.0, 1.0, 0.0)]) + [1, 7] + +Note how these indices coincide with the correct indices in the +subcenters call above. + +subtolerance +^^^^^^^^^^^^ + +subtolerance returns the min, average, and max tolerance on every +subshape. When called with 'all', it iterates through all subshape +types:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.subtolerance('edge') + (1.0001000025000001e-07, 1.0001000025000002e-07, 1.0001000025000001e-07) + +The vertex, edge, and face class also support a *tolerance* method, +which returns the tolerance of that shape itself. + +dump +^^^^ + +dump returns the index, center, and tolerance of every subshape:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.dump() + shell0 location: (0.500000,1.000000,1.500000) + face0 location: (0.000000,1.000000,1.500000) tolerance: 1.0000e-07 + face1 location: (1.000000,1.000000,1.500000) tolerance: 1.0000e-07 + face2 location: (0.500000,0.000000,1.500000) tolerance: 1.0000e-07 + face3 location: (0.500000,2.000000,1.500000) tolerance: 1.0000e-07 + face4 location: (0.500000,1.000000,0.000000) tolerance: 1.0000e-07 + face5 location: (0.500000,1.000000,3.000000) tolerance: 1.0000e-07 + wire0 location: (0.000000,1.000000,1.500000) + wire1 location: (1.000000,1.000000,1.500000) + wire2 location: (0.500000,0.000000,1.500000) + wire3 location: (0.500000,2.000000,1.500000) + wire4 location: (0.500000,1.000000,0.000000) + wire5 location: (0.500000,1.000000,3.000000) + edge0 location: (0.000000,0.000000,1.500000) tolerance: 1.0000e-07 + edge1 location: (0.000000,1.000000,3.000000) tolerance: 1.0000e-07 + edge2 location: (0.000000,2.000000,1.500000) tolerance: 1.0000e-07 + edge3 location: (0.000000,1.000000,0.000000) tolerance: 1.0000e-07 + edge4 location: (1.000000,0.000000,1.500000) tolerance: 1.0000e-07 + edge5 location: (1.000000,1.000000,3.000000) tolerance: 1.0000e-07 + edge6 location: (1.000000,2.000000,1.500000) tolerance: 1.0000e-07 + edge7 location: (1.000000,1.000000,0.000000) tolerance: 1.0000e-07 + edge8 location: (0.500000,0.000000,0.000000) tolerance: 1.0000e-07 + edge9 location: (0.500000,0.000000,3.000000) tolerance: 1.0000e-07 + edge10 location: (0.500000,2.000000,0.000000) tolerance: 1.0000e-07 + edge11 location: (0.500000,2.000000,3.000000) tolerance: 1.0000e-07 + vertex0 location: (0.000000,0.000000,3.000000) tolerance: 1.0000e-07 + vertex1 location: (0.000000,0.000000,0.000000) tolerance: 1.0000e-07 + vertex2 location: (0.000000,2.000000,3.000000) tolerance: 1.0000e-07 + vertex3 location: (0.000000,2.000000,0.000000) tolerance: 1.0000e-07 + vertex4 location: (1.000000,0.000000,3.000000) tolerance: 1.0000e-07 + vertex5 location: (1.000000,0.000000,0.000000) tolerance: 1.0000e-07 + vertex6 location: (1.000000,2.000000,3.000000) tolerance: 1.0000e-07 + vertex7 location: (1.000000,2.000000,0.000000) tolerance: 1.0000e-07 + +Output may also be printed as a hierarchy. + +vertex +------ + +The vertex class supports no additional methods. + +edge +---- + +type +^^^^ + +type returns the type of edge:: + + e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2) + e1.type() + 'ellipse' + +length +^^^^^^ + +length returns the length of the edge:: + + e1 = ccad.circle(1.0) + e1.length() + 6.2831853071795862 + +poly +^^^^ + +poly returns a polyline approximation to an edge:: + + e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2) + e1.poly() + [(-3.6738190614671318e-16, 1.0, 0.0), + (0.15691819145569041, 0.99691733373312796, 0.0), + (0.31286893008046135, 0.98768834059513777, 0.0), + (0.46689072771180956, 0.97236992039767678, 0.0), + (0.61803398874989446, 0.95105651629515364, 0.0), + (0.76536686473018001, 0.92387953251128663, 0.0), + (0.90798099947909328, 0.8910065241883679, 0.0), + (1.0449971294318967, 0.85264016435409251, 0.0), + (1.1755705045849458, 0.80901699437494756, 0.0), + (1.2988960966603678, 0.76040596560003082, 0.0), + (1.4142135623730947, 0.70710678118654768, 0.0), + (1.520811931200061, 0.64944804833018421, 0.0), + (1.5706338617614892, 0.6190939493098343, 0.0), + (1.6180339887498947, 0.58778525229247336, 0.0), + (1.6691465074426051, 0.55089698145210275, 0.0), + (1.716897587203732, 0.51289927740590635, 0.0), + (1.7696268722994766, 0.46594547235582523, 0.0), + (1.8172246744657483, 0.41764053997213163, 0.0), + (1.8460369350307941, 0.38475564794493639, 0.0), + (1.8724697412794742, 0.35137482408134291, 0.0), + (1.8964890225744522, 0.3175410946848452, 0.0), + (1.9180638191979309, 0.28329806983275019, 0.0), + (1.9371663222572619, 0.24868988716485535, 0.0), + (1.9537719095292947, 0.21376115499211573, 0.0), + (1.9678591771972589, 0.17855689479863776, 0.0), + (1.9794099674392696, 0.14312248321112042, 0.0), + (1.9884093918329029, 0.10750359351052567, 0.0), + (1.9948458505456679, 0.071746136761379878, 0.0), + (1.9987110472866427, 0.035896202634582597, 0.0), + (2.0, 2.4492127076447545e-16, 0.0)] + +wire +---- + +The wire class can be called with a list of edges to be combined into +a wire. + +length +^^^^^^ + +Like the length method in edge, edge returns the length of the wire. + +poly +^^^^ + +Like the poly method in edge, poly returns a polyline +approximation to a wire. + +face +---- + +fillet +^^^^^^ + +fillet allows you to fillet a face at passed vertices. The following +example fillets the upper right corner and lower left corner of a square:: + + w1 = ccad.polygon([(-1.0, -1.0, 0.0), + (1.0, -1.0, 0.0), + (1.0, 1.0, 0.0), + (-1.0, 1.0, 0.0), + (-1.0, -1.0, 0.0)]) + f1 = ccad.plane(w1) + f1.fillet(0.25, [(1.0, 1.0, 0.0), (-1.0, -1.0, 0.0)])) + +.. image:: logging_face_fillet.png + +wire +^^^^ + +wire returns the outer wire of a face. + +inner_wires +^^^^^^^^^^^ + +inner_wires returns the inner wires of a face. + +type +^^^^ + +type returns the type of mathematical surface a face sits on:: + + s1 = ccad.cone(4.0, 2.0, 2.0) + faces = s1.subshapes('face') + map(lambda x: x.type(), faces) + ['cone', 'plane', 'plane'] + +area +^^^^ + +area returns the area of the face:: + + f1 = ccad.plane(cm.wire([ccad.circle(1.0)])) + f1.area() + 3.141592653589794 + +shell +----- + +The shell class can be called with a list of faces to be sewed into a +shell. + +area +^^^^ + +Like the area method in face, area returns the area of the shell. + +solid +----- + +The solid class can be called with a list of shells to be combined +into a solid. + +fillet +^^^^^^ + +fillet allows you to fillet edges. The following example fillets all +the edges on the positive x-side of the cube:: + + s1 = ccad.box(1.0, 1.0, 1.0) + s1.fillet(0.25, [(1.0, 0.5, 0.0), + (1.0, 0.5, 1.0), + (1.0, 0.0, 0.5), + (1.0, 1.0, 0.5)]) + +.. image:: logging_solid_fillet.png + +Filleting can be buggy. I found the following things helped improve +success rate: + + - Eliminate impossible conditions (e.g. fillet radius is 0.6 on a + 1x1x1 box). + + - Eliminate unneeded edges. OCC's boolean operations often return + two faces in the same domain with an edge between them that can be + merged. Eliminating these edges by merging the faces helped. The + *simplify* method can do this for some shapes. + + - Move problem edges out of the way. Cylinder and sphere edges are + necessary in OCC, but their position can often be rotated away + from a problem fillet location. + + - Slice the solid (with a box or something) into sections. Then + fuse those sections back together. Then fillet. + + - Change the fillet radius slightly. + + - Fillet a few edges, then a few more, then a few more, etc. For + example, if you have a shape with multiple pockets, fillet one + pocket, then the next, then the next. The fillet operation tended + to fail when the number of fillets got large. + +chamfer +^^^^^^^ + +chamfer allows you to chamfer edges. The following example chamfers +three edges in a cube:: + + s1 = ccad.box(1.0, 1.0, 1.0) + s1.chamfer(0.25, [(1.0, 0.5, 0.0), + (1.0, 0.5, 1.0), + (1.0, 0.0, 0.5), + (1.0, 1.0, 0.5)]) + +.. image:: logging_solid_chamfer.png + +draft +^^^^^ + +draft places a draft on the faces specified. The following example +drafts the vertical edges of a cube:: + + s1 = ccad.box(1.0, 1.0, 1.0) + s1.translate((-0.5, -0.5, 0.0)) + face_centers = s1.subcenters('face') + to_draft = [] + for count, face_center in enumerate(face_centers): + if abs(face_center[2] - 0.5) < 0.1: + to_draft.append(count) + s1.draft(math.radians(5.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0), to_draft) + +.. image:: logging_solid_draft.png + +volume +^^^^^^ + +volume returns the volume in cubic units of the solid:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.volume() + 6.0 + +simplify +^^^^^^^^ + +Boolean operations can often leave more faces than are necessary, +particularly when faces are coincident. OCC hasn't fixed this issue, +so I fixed it, although my implementation won't fix all cases:: + + s1 = ccad.box(1.0, 1.0, 1.0) + s2 = s1.copy() + s2.translate((1.0, 0.5, 0.5)) + s3 = s1 - s2 + len(s3.subshapes('face')) + 7 + +.. image:: logging_solid_simplify1.png + +Note **s3** has an extra face. (I've only drawn edges, but you get the +point). Now, let's simplify it:: + + s3.simplify() + len(s3.subshapes('face')) + 6 + +.. image:: logging_solid_simplify2.png + +Note the extra face is removed. + diff --git a/src/contrib/ccad/doc/html/_sources/modelling.txt b/src/contrib/ccad/doc/html/_sources/modelling.txt new file mode 100644 index 000000000..1e9337d21 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/modelling.txt @@ -0,0 +1,63 @@ +Modelling +========= + +Modelling describes creating models in ccad. To understand modelling +well, it's helpful to understand how a model is described in ccad. +ccad models are defined by a sequence of simpler shapes and +boundaries. In CAD terminology, it's called a Boundary Representation +model, or BRep, for short. Consider a cube. + +.. image:: cube_solid.png + +The entire cube is called a **solid**. A solid is made of one or more +outer layers called **shells**. For example, if the cube had a hollow +center, it would be made of two shells. One shell would define the +outside of the cube. The other would define the inside. Each shell +is made of one or more **faces**. + +.. image:: cube_face.png + +Each face has a single mathematical expression that defines the whole +face. Planes and spheres are examples of faces. Each face is bounded +by one or more **wires**. + +.. image:: cube_wire.png + +The wires define the boundary of the face. Each wire is made of one or +more **edges**. + +.. image:: cube_edge.png + +Edges, like faces, have a unique mathematical expression that defines +the entire edge. Each edge is bounded by two **vertices**. + +.. image:: cube_vertex.png + +Put in a hierarchy, it looks like this + + solid + shell + face + wire + edge + vertex + +ccad calls all of these **shapes**. Each shape has its own class in +ccad. During modelling, you may need to use all of these classes. I +use ``wire`` much. I use the others rarely. Usually, I'm calling a +higher level function, like sphere, that instantiates a solid, or arc, +that instantiates an edge. + +.. toctree:: + :maxdepth: 2 + + creating_edges + creating_wires + creating_faces + creating_solids + creating_derived + trms + boolean + logging + file_transfer + diff --git a/src/contrib/ccad/doc/html/_sources/stand_alone.txt b/src/contrib/ccad/doc/html/_sources/stand_alone.txt new file mode 100644 index 000000000..0c19a8acf --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/stand_alone.txt @@ -0,0 +1,37 @@ +Stand-Alone Viewing +=================== + +To run the viewer stand-alone, from your own python program, call +ccad.display() on all the shapes you'd like to see, then call +ccad.start(), which will start the GUI (Graphical User Interface). +The following python program creates a sphere and a box, calls the +display function on each, then starts the viewer: + +.. literalinclude:: display_spherebox1.py + +If you've read about modelling, you should understand the listing up +to the view line:: + + v1 = ccad.view() + +which instantiates a ccad viewing window. The next line:: + + v1.display(s1) + +displays s1 in the v1 window. And:: + + v1.display(s2) + +displays s2 in the v1 window. The final line:: + + ccad.start() + +starts the viewer. Once you start the viewer, the program is locked +until you exit the viewer. Usually, you want to make this the last +line in your code. + +After running, the following window appears + +.. image:: display_spherebox1.png + +Now, you can view your design. diff --git a/src/contrib/ccad/doc/html/_sources/trms.txt b/src/contrib/ccad/doc/html/_sources/trms.txt new file mode 100644 index 000000000..890d3afaf --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/trms.txt @@ -0,0 +1,85 @@ +Moving, Rotating, Mirroring, and Scaling +======================================== + +translate, rotate, mirror, and scale on shapes can be called with a +class method. For example:: + + s1.translate((1.0, 0.0, 0.0)) + +This translates *s1* in-place. ccad comes with a function call form +too with "ed" appended. For example:: + + s2 = ccad.translated(s1, (1.0, 0.0, 0.0)) + +The function form returns a new shape. *s2* is *s1* translated. *s1* +is unchanged. It can be useful when you want to save the old shape or +write code with fewer lines. + +In each of the following examples, **s1** describes the old object +drawn in **red**, and **s2** describes the new object drawn in **blue**. + +Moving +------ + +translate changes a shape's position but not its orientation. The +following example moves the box 2.0 in the x-direction, -6.0 in the +y-direction, and 4.0 in the z-direction:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.translated(s1, (2.0, -6.0, 4.0)) + +.. image:: trms_translate.png + +Rotation +-------- + +rotate rotates a shape about a point and a direction vector. Rotation +follows the right hand rule. The following example rotates the box 90 +degrees about the z-axis:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.rotated(s1, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), math.pi/2) + +.. image:: trms_rotate.png + +There are also three shorthand notations when rotation is about the +origin and a single axis: **rotatex**, **rotatey**, and **rotatez** +(or **rotatedx**, **rotatedy**, and **rotatedz** in function form). +The following example performs the same rotation above but in +shorthand:: + + s2 = ccad.rotatedz(math.pi/2) + +Mirroring +--------- + +mirror mirrors the shape about a point in a given direction. The +following example mirrors the box about the origin in the x-direction:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.mirrored(s1, (0.0, 0.0, 0.0), (1.0, 0.0, 0.0)) + +.. image:: trms_mirror.png + +Like rotate, shorthand versions exist of **mirrorx**, **mirrory**, and +**mirrorz** (or **mirroredx**, **mirroredy**, and **mirroredz** in +function form). + +Scaling +------- + +scale changes the size of the shape by scale factors. The following +example scales the box in the x-direction and then moves it:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.scaled(s1, 2.0, 1.0, 1.0) + s2.translate((4.0, 0.0, 0.0)) + +.. image:: trms_scale.png + +Passing a single parameter to scale scales all three dimensions the +same. + +Like rotate and mirror, shorthand versions exist of **scalex**, +**scaley**, and **scalez** (or **scaledx**, **scaledy**, and +**scaledz** in function form). diff --git a/src/contrib/ccad/doc/html/_sources/using_display.txt b/src/contrib/ccad/doc/html/_sources/using_display.txt new file mode 100644 index 000000000..00af73fb3 --- /dev/null +++ b/src/contrib/ccad/doc/html/_sources/using_display.txt @@ -0,0 +1,100 @@ +Using Display +============= + +View -> Mode +------------ + +ccad offers three forms of viewing: wireframe, shaded (filled solids), +and hidden line removal. + +.. image:: display_wireframe.png +.. image:: display_shaded.png +.. image:: display_hlr.png + +View -> Side +------------ + +Various standard views can be selected from the View -> Side menu, or +you can use Blender hotkeys: + + - keypad 7 for top view + - keypad 1 for front view + - keypad 3 for right view + - hold shift with those keys to see the opposite view + +View -> Orbit +------------- + +Orbiting allows you to orbit around the scene, keeping the scene the +same distance from you. You can use the menus, you can press the +middle mouse button and move the mouse, or you can use Blender hotkeys: + + - keypad 8 to orbit up + - keypad 2 to orbit down + - keypad 4 to orbit left + - keypad 6 to orbit right + +View -> Pan +----------- + +Pan allows you to move the center of the scene. You can use the +menus, you can hold shift while pressing the middle mouse button and +moving the mouse, or you can use Blender hotkeys: + + - shift keypad 8 to pan up + - shift keypad 2 to pan down + - shift keypad 4 to pan left + - shift keypad 6 to pan right + + +View -> Zoom +------------ + +You can zoom in or out by using the menus, or using the Blender hotkeys: + + - keypad + to zoom in + - keypad - to zoom out + +Select -> Shape +--------------- + +In Select -> Shape mode, you select shapes. Move to the shape you are +interested in, and right click on it. The shape is selected. In this +case, shape refers to something you passed to the display function, +not any arbitrary shape. If you only passed one shape to the display +function, there is only one shape to select. + +Select -> Face +-------------- + +In Select -> Face mode, you can select a face on a shape you selected. +Right click on the face of interest, and you'll see the face index, +type, and center on the status line. + +.. image:: select_face.png + +Select -> Edge +-------------- + +In Select -> Edge mode, you can select an edge on a shape you +selected. Right click on the edge of interest, and you'll see the +edge index and center on the status line. + +.. image:: select_edge.png + +Edge selection can be very handy when trying to fillet specific edges. +Once you know the edge's coordinates (or even index if the shape won't +change), you can pass those coordinates to the fillet function. + +Select -> Vertex +---------------- + +In Select -> Vertex mode, you can select a vertex on a shape you +selected. Right click on the vertex of interest, and you'll see the +vertex index and center on the status line. + +.. image:: select_vertex.png + + + + diff --git a/src/contrib/ccad/doc/html/_static/basic.css b/src/contrib/ccad/doc/html/_static/basic.css new file mode 100644 index 000000000..a04d6545b --- /dev/null +++ b/src/contrib/ccad/doc/html/_static/basic.css @@ -0,0 +1,417 @@ +/** + * Sphinx stylesheet -- basic theme + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +img { + border: 0; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li div.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable dl, table.indextable dd { + margin-top: 0; + margin-bottom: 0; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +/* -- general body styles --------------------------------------------------- */ + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.field-list ul { + padding-left: 1em; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px 7px 0 7px; + background-color: #ffe; + width: 40%; + float: right; +} + +p.sidebar-title { + font-weight: bold; +} + +/* -- topics ---------------------------------------------------------------- */ + +div.topic { + border: 1px solid #ccc; + padding: 7px 7px 0 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +div.admonition dl { + margin-bottom: 0; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + border: 0; + border-collapse: collapse; +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 0; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +table.field-list td, table.field-list th { + border: 0 !important; +} + +table.footnote td, table.footnote th { + border: 0 !important; +} + +th { + text-align: left; + padding-right: 5px; +} + +/* -- other body styles ----------------------------------------------------- */ + +dl { + margin-bottom: 15px; +} + +dd p { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dt:target, .highlight { + background-color: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.refcount { + color: #060; +} + +.optional { + font-size: 1.3em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; +} + +td.linenos pre { + padding: 5px 0px; + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + margin-left: 0.5em; +} + +table.highlighttable td { + padding: 0 0.5em 0 0.5em; +} + +tt.descname { + background-color: transparent; + font-weight: bold; + font-size: 1.2em; +} + +tt.descclassname { + background-color: transparent; +} + +tt.xref, a tt { + background-color: transparent; + font-weight: bold; +} + +h1 tt, h2 tt, h3 tt, h4 tt, h5 tt, h6 tt { + background-color: transparent; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} diff --git a/src/contrib/ccad/doc/html/_static/contents.png b/src/contrib/ccad/doc/html/_static/contents.png new file mode 100644 index 000000000..7fb82154a Binary files /dev/null and b/src/contrib/ccad/doc/html/_static/contents.png differ diff --git a/src/contrib/ccad/doc/html/_static/doctools.js b/src/contrib/ccad/doc/html/_static/doctools.js new file mode 100644 index 000000000..9447678cd --- /dev/null +++ b/src/contrib/ccad/doc/html/_static/doctools.js @@ -0,0 +1,232 @@ +/// XXX: make it cross browser + +/** + * make the code below compatible with browsers without + * an installed firebug like debugger + */ +if (!window.console || !console.firebug) { + var names = ["log", "debug", "info", "warn", "error", "assert", "dir", "dirxml", + "group", "groupEnd", "time", "timeEnd", "count", "trace", "profile", "profileEnd"]; + window.console = {}; + for (var i = 0; i < names.length; ++i) + window.console[names[i]] = function() {} +} + +/** + * small helper function to urldecode strings + */ +jQuery.urldecode = function(x) { + return decodeURIComponent(x).replace(/\+/g, ' '); +} + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s == 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +} + +/** + * small function to check if an array contains + * a given item. + */ +jQuery.contains = function(arr, item) { + for (var i = 0; i < arr.length; i++) { + if (arr[i] == item) + return true; + } + return false; +} + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node) { + if (node.nodeType == 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && !jQuery.className.has(node.parentNode, className)) { + var span = document.createElement("span"); + span.className = className; + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this) + }); + } + } + return this.each(function() { + highlight(this); + }); +} + +/** + * Small JavaScript module for the documentation. + */ +var Documentation = { + + init : function() { + this.fixFirefoxAnchorBug(); + this.highlightSearchWords(); + this.initModIndex(); + }, + + /** + * i18n support + */ + TRANSLATIONS : {}, + PLURAL_EXPR : function(n) { return n == 1 ? 0 : 1; }, + LOCALE : 'unknown', + + // gettext and ngettext don't access this so that the functions + // can savely bound to a different name (_ = Documentation.gettext) + gettext : function(string) { + var translated = Documentation.TRANSLATIONS[string]; + if (typeof translated == 'undefined') + return string; + return (typeof translated == 'string') ? translated : translated[0]; + }, + + ngettext : function(singular, plural, n) { + var translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated == 'undefined') + return (n == 1) ? singular : plural; + return translated[Documentation.PLURALEXPR(n)]; + }, + + addTranslations : function(catalog) { + for (var key in catalog.messages) + this.TRANSLATIONS[key] = catalog.messages[key]; + this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); + this.LOCALE = catalog.locale; + }, + + /** + * add context elements like header anchor links + */ + addContextElements : function() { + $('div[id] > :header:first').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this headline')). + appendTo(this); + }); + $('dt[id]').each(function() { + $('\u00B6'). + attr('href', '#' + this.id). + attr('title', _('Permalink to this definition')). + appendTo(this); + }); + }, + + /** + * workaround a firefox stupidity + */ + fixFirefoxAnchorBug : function() { + if (document.location.hash && $.browser.mozilla) + window.setTimeout(function() { + document.location.href += ''; + }, 10); + }, + + /** + * highlight the search words provided in the url in the text + */ + highlightSearchWords : function() { + var params = $.getQueryParameters(); + var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; + if (terms.length) { + var body = $('div.body'); + window.setTimeout(function() { + $.each(terms, function() { + body.highlightText(this.toLowerCase(), 'highlight'); + }); + }, 10); + $('') + .appendTo($('.sidebar .this-page-menu')); + } + }, + + /** + * init the modindex toggle buttons + */ + initModIndex : function() { + var togglers = $('img.toggler').click(function() { + var src = $(this).attr('src'); + var idnum = $(this).attr('id').substr(7); + console.log($('tr.cg-' + idnum).toggle()); + if (src.substr(-9) == 'minus.png') + $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); + else + $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); + }).css('display', ''); + if (DOCUMENTATION_OPTIONS.COLLAPSE_MODINDEX) { + togglers.click(); + } + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords : function() { + $('.sidebar .this-page-menu li.highlight-link').fadeOut(300); + $('span.highlight').removeClass('highlight'); + }, + + /** + * make the url absolute + */ + makeURL : function(relativeURL) { + return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; + }, + + /** + * get the current relative url + */ + getCurrentURL : function() { + var path = document.location.pathname; + var parts = path.split(/\//); + $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { + if (this == '..') + parts.pop(); + }); + var url = parts.join('/'); + return path.substring(url.lastIndexOf('/') + 1, path.length - 1); + } +}; + +// quick alias for translations +_ = Documentation.gettext; + +$(document).ready(function() { + Documentation.init(); +}); diff --git a/src/contrib/ccad/doc/html/_static/file.png b/src/contrib/ccad/doc/html/_static/file.png new file mode 100644 index 000000000..d18082e39 Binary files /dev/null and b/src/contrib/ccad/doc/html/_static/file.png differ diff --git a/src/contrib/ccad/doc/html/_static/jquery.js b/src/contrib/ccad/doc/html/_static/jquery.js new file mode 100644 index 000000000..fff677643 --- /dev/null +++ b/src/contrib/ccad/doc/html/_static/jquery.js @@ -0,0 +1,6240 @@ +/*! + * jQuery JavaScript Library v1.4.2 + * http://jquery.com/ + * + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2010, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Sat Feb 13 22:33:48 2010 -0500 + */ +(function( window, undefined ) { + +// Define a local copy of jQuery +var jQuery = function( selector, context ) { + // The jQuery object is actually just the init constructor 'enhanced' + return new jQuery.fn.init( selector, context ); + }, + + // Map over jQuery in case of overwrite + _jQuery = window.jQuery, + + // Map over the $ in case of overwrite + _$ = window.$, + + // Use the correct document accordingly with window argument (sandbox) + document = window.document, + + // A central reference to the root jQuery(document) + rootjQuery, + + // A simple way to check for HTML strings or ID strings + // (both of which we optimize for) + quickExpr = /^[^<]*(<[\w\W]+>)[^>]*$|^#([\w-]+)$/, + + // Is it a simple selector + isSimple = /^.[^:#\[\.,]*$/, + + // Check if a string has a non-whitespace character in it + rnotwhite = /\S/, + + // Used for trimming whitespace + rtrim = /^(\s|\u00A0)+|(\s|\u00A0)+$/g, + + // Match a standalone tag + rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, + + // Keep a UserAgent string for use with jQuery.browser + userAgent = navigator.userAgent, + + // For matching the engine and version of the browser + browserMatch, + + // Has the ready events already been bound? + readyBound = false, + + // The functions to execute on DOM ready + readyList = [], + + // The ready event handler + DOMContentLoaded, + + // Save a reference to some core methods + toString = Object.prototype.toString, + hasOwnProperty = Object.prototype.hasOwnProperty, + push = Array.prototype.push, + slice = Array.prototype.slice, + indexOf = Array.prototype.indexOf; + +jQuery.fn = jQuery.prototype = { + init: function( selector, context ) { + var match, elem, ret, doc; + + // Handle $(""), $(null), or $(undefined) + if ( !selector ) { + return this; + } + + // Handle $(DOMElement) + if ( selector.nodeType ) { + this.context = this[0] = selector; + this.length = 1; + return this; + } + + // The body element only exists once, optimize finding it + if ( selector === "body" && !context ) { + this.context = document; + this[0] = document.body; + this.selector = "body"; + this.length = 1; + return this; + } + + // Handle HTML strings + if ( typeof selector === "string" ) { + // Are we dealing with HTML string or an ID? + match = quickExpr.exec( selector ); + + // Verify a match, and that no context was specified for #id + if ( match && (match[1] || !context) ) { + + // HANDLE: $(html) -> $(array) + if ( match[1] ) { + doc = (context ? context.ownerDocument || context : document); + + // If a single string is passed in and it's a single tag + // just do a createElement and skip the rest + ret = rsingleTag.exec( selector ); + + if ( ret ) { + if ( jQuery.isPlainObject( context ) ) { + selector = [ document.createElement( ret[1] ) ]; + jQuery.fn.attr.call( selector, context, true ); + + } else { + selector = [ doc.createElement( ret[1] ) ]; + } + + } else { + ret = buildFragment( [ match[1] ], [ doc ] ); + selector = (ret.cacheable ? ret.fragment.cloneNode(true) : ret.fragment).childNodes; + } + + return jQuery.merge( this, selector ); + + // HANDLE: $("#id") + } else { + elem = document.getElementById( match[2] ); + + if ( elem ) { + // Handle the case where IE and Opera return items + // by name instead of ID + if ( elem.id !== match[2] ) { + return rootjQuery.find( selector ); + } + + // Otherwise, we inject the element directly into the jQuery object + this.length = 1; + this[0] = elem; + } + + this.context = document; + this.selector = selector; + return this; + } + + // HANDLE: $("TAG") + } else if ( !context && /^\w+$/.test( selector ) ) { + this.selector = selector; + this.context = document; + selector = document.getElementsByTagName( selector ); + return jQuery.merge( this, selector ); + + // HANDLE: $(expr, $(...)) + } else if ( !context || context.jquery ) { + return (context || rootjQuery).find( selector ); + + // HANDLE: $(expr, context) + // (which is just equivalent to: $(context).find(expr) + } else { + return jQuery( context ).find( selector ); + } + + // HANDLE: $(function) + // Shortcut for document ready + } else if ( jQuery.isFunction( selector ) ) { + return rootjQuery.ready( selector ); + } + + if (selector.selector !== undefined) { + this.selector = selector.selector; + this.context = selector.context; + } + + return jQuery.makeArray( selector, this ); + }, + + // Start with an empty selector + selector: "", + + // The current version of jQuery being used + jquery: "1.4.2", + + // The default length of a jQuery object is 0 + length: 0, + + // The number of elements contained in the matched element set + size: function() { + return this.length; + }, + + toArray: function() { + return slice.call( this, 0 ); + }, + + // Get the Nth element in the matched element set OR + // Get the whole matched element set as a clean array + get: function( num ) { + return num == null ? + + // Return a 'clean' array + this.toArray() : + + // Return just the object + ( num < 0 ? this.slice(num)[ 0 ] : this[ num ] ); + }, + + // Take an array of elements and push it onto the stack + // (returning the new matched element set) + pushStack: function( elems, name, selector ) { + // Build a new jQuery matched element set + var ret = jQuery(); + + if ( jQuery.isArray( elems ) ) { + push.apply( ret, elems ); + + } else { + jQuery.merge( ret, elems ); + } + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + + ret.context = this.context; + + if ( name === "find" ) { + ret.selector = this.selector + (this.selector ? " " : "") + selector; + } else if ( name ) { + ret.selector = this.selector + "." + name + "(" + selector + ")"; + } + + // Return the newly-formed element set + return ret; + }, + + // Execute a callback for every element in the matched set. + // (You can seed the arguments with an array of args, but this is + // only used internally.) + each: function( callback, args ) { + return jQuery.each( this, callback, args ); + }, + + ready: function( fn ) { + // Attach the listeners + jQuery.bindReady(); + + // If the DOM is already ready + if ( jQuery.isReady ) { + // Execute the function immediately + fn.call( document, jQuery ); + + // Otherwise, remember the function for later + } else if ( readyList ) { + // Add the function to the wait list + readyList.push( fn ); + } + + return this; + }, + + eq: function( i ) { + return i === -1 ? + this.slice( i ) : + this.slice( i, +i + 1 ); + }, + + first: function() { + return this.eq( 0 ); + }, + + last: function() { + return this.eq( -1 ); + }, + + slice: function() { + return this.pushStack( slice.apply( this, arguments ), + "slice", slice.call(arguments).join(",") ); + }, + + map: function( callback ) { + return this.pushStack( jQuery.map(this, function( elem, i ) { + return callback.call( elem, i, elem ); + })); + }, + + end: function() { + return this.prevObject || jQuery(null); + }, + + // For internal use only. + // Behaves like an Array's method, not like a jQuery method. + push: push, + sort: [].sort, + splice: [].splice +}; + +// Give the init function the jQuery prototype for later instantiation +jQuery.fn.init.prototype = jQuery.fn; + +jQuery.extend = jQuery.fn.extend = function() { + // copy reference to target object + var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options, name, src, copy; + + // Handle a deep copy situation + if ( typeof target === "boolean" ) { + deep = target; + target = arguments[1] || {}; + // skip the boolean and the target + i = 2; + } + + // Handle case when target is a string or something (possible in deep copy) + if ( typeof target !== "object" && !jQuery.isFunction(target) ) { + target = {}; + } + + // extend jQuery itself if only one argument is passed + if ( length === i ) { + target = this; + --i; + } + + for ( ; i < length; i++ ) { + // Only deal with non-null/undefined values + if ( (options = arguments[ i ]) != null ) { + // Extend the base object + for ( name in options ) { + src = target[ name ]; + copy = options[ name ]; + + // Prevent never-ending loop + if ( target === copy ) { + continue; + } + + // Recurse if we're merging object literal values or arrays + if ( deep && copy && ( jQuery.isPlainObject(copy) || jQuery.isArray(copy) ) ) { + var clone = src && ( jQuery.isPlainObject(src) || jQuery.isArray(src) ) ? src + : jQuery.isArray(copy) ? [] : {}; + + // Never move original objects, clone them + target[ name ] = jQuery.extend( deep, clone, copy ); + + // Don't bring in undefined values + } else if ( copy !== undefined ) { + target[ name ] = copy; + } + } + } + } + + // Return the modified object + return target; +}; + +jQuery.extend({ + noConflict: function( deep ) { + window.$ = _$; + + if ( deep ) { + window.jQuery = _jQuery; + } + + return jQuery; + }, + + // Is the DOM ready to be used? Set to true once it occurs. + isReady: false, + + // Handle when the DOM is ready + ready: function() { + // Make sure that the DOM is not already loaded + if ( !jQuery.isReady ) { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( !document.body ) { + return setTimeout( jQuery.ready, 13 ); + } + + // Remember that the DOM is ready + jQuery.isReady = true; + + // If there are functions bound, to execute + if ( readyList ) { + // Execute all of them + var fn, i = 0; + while ( (fn = readyList[ i++ ]) ) { + fn.call( document, jQuery ); + } + + // Reset the list of functions + readyList = null; + } + + // Trigger any bound ready events + if ( jQuery.fn.triggerHandler ) { + jQuery( document ).triggerHandler( "ready" ); + } + } + }, + + bindReady: function() { + if ( readyBound ) { + return; + } + + readyBound = true; + + // Catch cases where $(document).ready() is called after the + // browser event has already occurred. + if ( document.readyState === "complete" ) { + return jQuery.ready(); + } + + // Mozilla, Opera and webkit nightlies currently support this event + if ( document.addEventListener ) { + // Use the handy event callback + document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + + // A fallback to window.onload, that will always work + window.addEventListener( "load", jQuery.ready, false ); + + // If IE event model is used + } else if ( document.attachEvent ) { + // ensure firing before onload, + // maybe late but safe also for iframes + document.attachEvent("onreadystatechange", DOMContentLoaded); + + // A fallback to window.onload, that will always work + window.attachEvent( "onload", jQuery.ready ); + + // If IE and not a frame + // continually check to see if the document is ready + var toplevel = false; + + try { + toplevel = window.frameElement == null; + } catch(e) {} + + if ( document.documentElement.doScroll && toplevel ) { + doScrollCheck(); + } + } + }, + + // See test/unit/core.js for details concerning isFunction. + // Since version 1.3, DOM methods and functions like alert + // aren't supported. They return false on IE (#2968). + isFunction: function( obj ) { + return toString.call(obj) === "[object Function]"; + }, + + isArray: function( obj ) { + return toString.call(obj) === "[object Array]"; + }, + + isPlainObject: function( obj ) { + // Must be an Object. + // Because of IE, we also have to check the presence of the constructor property. + // Make sure that DOM nodes and window objects don't pass through, as well + if ( !obj || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { + return false; + } + + // Not own constructor property must be Object + if ( obj.constructor + && !hasOwnProperty.call(obj, "constructor") + && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) { + return false; + } + + // Own properties are enumerated firstly, so to speed up, + // if last one is own, then all properties are own. + + var key; + for ( key in obj ) {} + + return key === undefined || hasOwnProperty.call( obj, key ); + }, + + isEmptyObject: function( obj ) { + for ( var name in obj ) { + return false; + } + return true; + }, + + error: function( msg ) { + throw msg; + }, + + parseJSON: function( data ) { + if ( typeof data !== "string" || !data ) { + return null; + } + + // Make sure leading/trailing whitespace is removed (IE can't handle it) + data = jQuery.trim( data ); + + // Make sure the incoming data is actual JSON + // Logic borrowed from http://json.org/json2.js + if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") + .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") + .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { + + // Try to use the native JSON parser first + return window.JSON && window.JSON.parse ? + window.JSON.parse( data ) : + (new Function("return " + data))(); + + } else { + jQuery.error( "Invalid JSON: " + data ); + } + }, + + noop: function() {}, + + // Evalulates a script in a global context + globalEval: function( data ) { + if ( data && rnotwhite.test(data) ) { + // Inspired by code by Andrea Giammarchi + // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html + var head = document.getElementsByTagName("head")[0] || document.documentElement, + script = document.createElement("script"); + + script.type = "text/javascript"; + + if ( jQuery.support.scriptEval ) { + script.appendChild( document.createTextNode( data ) ); + } else { + script.text = data; + } + + // Use insertBefore instead of appendChild to circumvent an IE6 bug. + // This arises when a base node is used (#2709). + head.insertBefore( script, head.firstChild ); + head.removeChild( script ); + } + }, + + nodeName: function( elem, name ) { + return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); + }, + + // args is for internal usage only + each: function( object, callback, args ) { + var name, i = 0, + length = object.length, + isObj = length === undefined || jQuery.isFunction(object); + + if ( args ) { + if ( isObj ) { + for ( name in object ) { + if ( callback.apply( object[ name ], args ) === false ) { + break; + } + } + } else { + for ( ; i < length; ) { + if ( callback.apply( object[ i++ ], args ) === false ) { + break; + } + } + } + + // A special, fast, case for the most common use of each + } else { + if ( isObj ) { + for ( name in object ) { + if ( callback.call( object[ name ], name, object[ name ] ) === false ) { + break; + } + } + } else { + for ( var value = object[0]; + i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} + } + } + + return object; + }, + + trim: function( text ) { + return (text || "").replace( rtrim, "" ); + }, + + // results is for internal usage only + makeArray: function( array, results ) { + var ret = results || []; + + if ( array != null ) { + // The window, strings (and functions) also have 'length' + // The extra typeof function check is to prevent crashes + // in Safari 2 (See: #3039) + if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { + push.call( ret, array ); + } else { + jQuery.merge( ret, array ); + } + } + + return ret; + }, + + inArray: function( elem, array ) { + if ( array.indexOf ) { + return array.indexOf( elem ); + } + + for ( var i = 0, length = array.length; i < length; i++ ) { + if ( array[ i ] === elem ) { + return i; + } + } + + return -1; + }, + + merge: function( first, second ) { + var i = first.length, j = 0; + + if ( typeof second.length === "number" ) { + for ( var l = second.length; j < l; j++ ) { + first[ i++ ] = second[ j ]; + } + + } else { + while ( second[j] !== undefined ) { + first[ i++ ] = second[ j++ ]; + } + } + + first.length = i; + + return first; + }, + + grep: function( elems, callback, inv ) { + var ret = []; + + // Go through the array, only saving the items + // that pass the validator function + for ( var i = 0, length = elems.length; i < length; i++ ) { + if ( !inv !== !callback( elems[ i ], i ) ) { + ret.push( elems[ i ] ); + } + } + + return ret; + }, + + // arg is for internal usage only + map: function( elems, callback, arg ) { + var ret = [], value; + + // Go through the array, translating each of the items to their + // new value (or values). + for ( var i = 0, length = elems.length; i < length; i++ ) { + value = callback( elems[ i ], i, arg ); + + if ( value != null ) { + ret[ ret.length ] = value; + } + } + + return ret.concat.apply( [], ret ); + }, + + // A global GUID counter for objects + guid: 1, + + proxy: function( fn, proxy, thisObject ) { + if ( arguments.length === 2 ) { + if ( typeof proxy === "string" ) { + thisObject = fn; + fn = thisObject[ proxy ]; + proxy = undefined; + + } else if ( proxy && !jQuery.isFunction( proxy ) ) { + thisObject = proxy; + proxy = undefined; + } + } + + if ( !proxy && fn ) { + proxy = function() { + return fn.apply( thisObject || this, arguments ); + }; + } + + // Set the guid of unique handler to the same of original handler, so it can be removed + if ( fn ) { + proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; + } + + // So proxy can be declared as an argument + return proxy; + }, + + // Use of jQuery.browser is frowned upon. + // More details: http://docs.jquery.com/Utilities/jQuery.browser + uaMatch: function( ua ) { + ua = ua.toLowerCase(); + + var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || + /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || + /(msie) ([\w.]+)/.exec( ua ) || + !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || + []; + + return { browser: match[1] || "", version: match[2] || "0" }; + }, + + browser: {} +}); + +browserMatch = jQuery.uaMatch( userAgent ); +if ( browserMatch.browser ) { + jQuery.browser[ browserMatch.browser ] = true; + jQuery.browser.version = browserMatch.version; +} + +// Deprecated, use jQuery.browser.webkit instead +if ( jQuery.browser.webkit ) { + jQuery.browser.safari = true; +} + +if ( indexOf ) { + jQuery.inArray = function( elem, array ) { + return indexOf.call( array, elem ); + }; +} + +// All jQuery objects should point back to these +rootjQuery = jQuery(document); + +// Cleanup functions for the document ready method +if ( document.addEventListener ) { + DOMContentLoaded = function() { + document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); + jQuery.ready(); + }; + +} else if ( document.attachEvent ) { + DOMContentLoaded = function() { + // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). + if ( document.readyState === "complete" ) { + document.detachEvent( "onreadystatechange", DOMContentLoaded ); + jQuery.ready(); + } + }; +} + +// The DOM ready check for Internet Explorer +function doScrollCheck() { + if ( jQuery.isReady ) { + return; + } + + try { + // If IE is used, use the trick by Diego Perini + // http://javascript.nwbox.com/IEContentLoaded/ + document.documentElement.doScroll("left"); + } catch( error ) { + setTimeout( doScrollCheck, 1 ); + return; + } + + // and execute any waiting functions + jQuery.ready(); +} + +function evalScript( i, elem ) { + if ( elem.src ) { + jQuery.ajax({ + url: elem.src, + async: false, + dataType: "script" + }); + } else { + jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); + } + + if ( elem.parentNode ) { + elem.parentNode.removeChild( elem ); + } +} + +// Mutifunctional method to get and set values to a collection +// The value/s can be optionally by executed if its a function +function access( elems, key, value, exec, fn, pass ) { + var length = elems.length; + + // Setting many attributes + if ( typeof key === "object" ) { + for ( var k in key ) { + access( elems, k, key[k], exec, fn, value ); + } + return elems; + } + + // Setting one attribute + if ( value !== undefined ) { + // Optionally, function values get executed if exec is true + exec = !pass && exec && jQuery.isFunction(value); + + for ( var i = 0; i < length; i++ ) { + fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); + } + + return elems; + } + + // Getting an attribute + return length ? fn( elems[0], key ) : undefined; +} + +function now() { + return (new Date).getTime(); +} +(function() { + + jQuery.support = {}; + + var root = document.documentElement, + script = document.createElement("script"), + div = document.createElement("div"), + id = "script" + now(); + + div.style.display = "none"; + div.innerHTML = "
a"; + + var all = div.getElementsByTagName("*"), + a = div.getElementsByTagName("a")[0]; + + // Can't get basic test support + if ( !all || !all.length || !a ) { + return; + } + + jQuery.support = { + // IE strips leading whitespace when .innerHTML is used + leadingWhitespace: div.firstChild.nodeType === 3, + + // Make sure that tbody elements aren't automatically inserted + // IE will insert them into empty tables + tbody: !div.getElementsByTagName("tbody").length, + + // Make sure that link elements get serialized correctly by innerHTML + // This requires a wrapper element in IE + htmlSerialize: !!div.getElementsByTagName("link").length, + + // Get the style information from getAttribute + // (IE uses .cssText insted) + style: /red/.test( a.getAttribute("style") ), + + // Make sure that URLs aren't manipulated + // (IE normalizes it by default) + hrefNormalized: a.getAttribute("href") === "/a", + + // Make sure that element opacity exists + // (IE uses filter instead) + // Use a regex to work around a WebKit issue. See #5145 + opacity: /^0.55$/.test( a.style.opacity ), + + // Verify style float existence + // (IE uses styleFloat instead of cssFloat) + cssFloat: !!a.style.cssFloat, + + // Make sure that if no value is specified for a checkbox + // that it defaults to "on". + // (WebKit defaults to "" instead) + checkOn: div.getElementsByTagName("input")[0].value === "on", + + // Make sure that a selected-by-default option has a working selected property. + // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) + optSelected: document.createElement("select").appendChild( document.createElement("option") ).selected, + + parentNode: div.removeChild( div.appendChild( document.createElement("div") ) ).parentNode === null, + + // Will be defined later + deleteExpando: true, + checkClone: false, + scriptEval: false, + noCloneEvent: true, + boxModel: null + }; + + script.type = "text/javascript"; + try { + script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); + } catch(e) {} + + root.insertBefore( script, root.firstChild ); + + // Make sure that the execution of code works by injecting a script + // tag with appendChild/createTextNode + // (IE doesn't support this, fails, and uses .text instead) + if ( window[ id ] ) { + jQuery.support.scriptEval = true; + delete window[ id ]; + } + + // Test to see if it's possible to delete an expando from an element + // Fails in Internet Explorer + try { + delete script.test; + + } catch(e) { + jQuery.support.deleteExpando = false; + } + + root.removeChild( script ); + + if ( div.attachEvent && div.fireEvent ) { + div.attachEvent("onclick", function click() { + // Cloning a node shouldn't copy over any + // bound event handlers (IE does this) + jQuery.support.noCloneEvent = false; + div.detachEvent("onclick", click); + }); + div.cloneNode(true).fireEvent("onclick"); + } + + div = document.createElement("div"); + div.innerHTML = ""; + + var fragment = document.createDocumentFragment(); + fragment.appendChild( div.firstChild ); + + // WebKit doesn't clone checked state correctly in fragments + jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; + + // Figure out if the W3C box model works as expected + // document.body must exist before we can do this + jQuery(function() { + var div = document.createElement("div"); + div.style.width = div.style.paddingLeft = "1px"; + + document.body.appendChild( div ); + jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; + document.body.removeChild( div ).style.display = 'none'; + + div = null; + }); + + // Technique from Juriy Zaytsev + // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ + var eventSupported = function( eventName ) { + var el = document.createElement("div"); + eventName = "on" + eventName; + + var isSupported = (eventName in el); + if ( !isSupported ) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + el = null; + + return isSupported; + }; + + jQuery.support.submitBubbles = eventSupported("submit"); + jQuery.support.changeBubbles = eventSupported("change"); + + // release memory in IE + root = script = div = all = a = null; +})(); + +jQuery.props = { + "for": "htmlFor", + "class": "className", + readonly: "readOnly", + maxlength: "maxLength", + cellspacing: "cellSpacing", + rowspan: "rowSpan", + colspan: "colSpan", + tabindex: "tabIndex", + usemap: "useMap", + frameborder: "frameBorder" +}; +var expando = "jQuery" + now(), uuid = 0, windowData = {}; + +jQuery.extend({ + cache: {}, + + expando:expando, + + // The following elements throw uncatchable exceptions if you + // attempt to add expando properties to them. + noData: { + "embed": true, + "object": true, + "applet": true + }, + + data: function( elem, name, data ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache; + + if ( !id && typeof name === "string" && data === undefined ) { + return null; + } + + // Compute a unique ID for the element + if ( !id ) { + id = ++uuid; + } + + // Avoid generating a new cache unless none exists and we + // want to manipulate it. + if ( typeof name === "object" ) { + elem[ expando ] = id; + thisCache = cache[ id ] = jQuery.extend(true, {}, name); + + } else if ( !cache[ id ] ) { + elem[ expando ] = id; + cache[ id ] = {}; + } + + thisCache = cache[ id ]; + + // Prevent overriding the named cache with undefined values + if ( data !== undefined ) { + thisCache[ name ] = data; + } + + return typeof name === "string" ? thisCache[ name ] : thisCache; + }, + + removeData: function( elem, name ) { + if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { + return; + } + + elem = elem == window ? + windowData : + elem; + + var id = elem[ expando ], cache = jQuery.cache, thisCache = cache[ id ]; + + // If we want to remove a specific section of the element's data + if ( name ) { + if ( thisCache ) { + // Remove the section of cache data + delete thisCache[ name ]; + + // If we've removed all the data, remove the element's cache + if ( jQuery.isEmptyObject(thisCache) ) { + jQuery.removeData( elem ); + } + } + + // Otherwise, we want to remove all of the element's data + } else { + if ( jQuery.support.deleteExpando ) { + delete elem[ jQuery.expando ]; + + } else if ( elem.removeAttribute ) { + elem.removeAttribute( jQuery.expando ); + } + + // Completely remove the data cache + delete cache[ id ]; + } + } +}); + +jQuery.fn.extend({ + data: function( key, value ) { + if ( typeof key === "undefined" && this.length ) { + return jQuery.data( this[0] ); + + } else if ( typeof key === "object" ) { + return this.each(function() { + jQuery.data( this, key ); + }); + } + + var parts = key.split("."); + parts[1] = parts[1] ? "." + parts[1] : ""; + + if ( value === undefined ) { + var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); + + if ( data === undefined && this.length ) { + data = jQuery.data( this[0], key ); + } + return data === undefined && parts[1] ? + this.data( parts[0] ) : + data; + } else { + return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function() { + jQuery.data( this, key, value ); + }); + } + }, + + removeData: function( key ) { + return this.each(function() { + jQuery.removeData( this, key ); + }); + } +}); +jQuery.extend({ + queue: function( elem, type, data ) { + if ( !elem ) { + return; + } + + type = (type || "fx") + "queue"; + var q = jQuery.data( elem, type ); + + // Speed up dequeue by getting out quickly if this is just a lookup + if ( !data ) { + return q || []; + } + + if ( !q || jQuery.isArray(data) ) { + q = jQuery.data( elem, type, jQuery.makeArray(data) ); + + } else { + q.push( data ); + } + + return q; + }, + + dequeue: function( elem, type ) { + type = type || "fx"; + + var queue = jQuery.queue( elem, type ), fn = queue.shift(); + + // If the fx queue is dequeued, always remove the progress sentinel + if ( fn === "inprogress" ) { + fn = queue.shift(); + } + + if ( fn ) { + // Add a progress sentinel to prevent the fx queue from being + // automatically dequeued + if ( type === "fx" ) { + queue.unshift("inprogress"); + } + + fn.call(elem, function() { + jQuery.dequeue(elem, type); + }); + } + } +}); + +jQuery.fn.extend({ + queue: function( type, data ) { + if ( typeof type !== "string" ) { + data = type; + type = "fx"; + } + + if ( data === undefined ) { + return jQuery.queue( this[0], type ); + } + return this.each(function( i, elem ) { + var queue = jQuery.queue( this, type, data ); + + if ( type === "fx" && queue[0] !== "inprogress" ) { + jQuery.dequeue( this, type ); + } + }); + }, + dequeue: function( type ) { + return this.each(function() { + jQuery.dequeue( this, type ); + }); + }, + + // Based off of the plugin by Clint Helfers, with permission. + // http://blindsignals.com/index.php/2009/07/jquery-delay/ + delay: function( time, type ) { + time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; + type = type || "fx"; + + return this.queue( type, function() { + var elem = this; + setTimeout(function() { + jQuery.dequeue( elem, type ); + }, time ); + }); + }, + + clearQueue: function( type ) { + return this.queue( type || "fx", [] ); + } +}); +var rclass = /[\n\t]/g, + rspace = /\s+/, + rreturn = /\r/g, + rspecialurl = /href|src|style/, + rtype = /(button|input)/i, + rfocusable = /(button|input|object|select|textarea)/i, + rclickable = /^(a|area)$/i, + rradiocheck = /radio|checkbox/; + +jQuery.fn.extend({ + attr: function( name, value ) { + return access( this, name, value, true, jQuery.attr ); + }, + + removeAttr: function( name, fn ) { + return this.each(function(){ + jQuery.attr( this, name, "" ); + if ( this.nodeType === 1 ) { + this.removeAttribute( name ); + } + }); + }, + + addClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.addClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( value && typeof value === "string" ) { + var classNames = (value || "").split( rspace ); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 ) { + if ( !elem.className ) { + elem.className = value; + + } else { + var className = " " + elem.className + " ", setClass = elem.className; + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { + setClass += " " + classNames[c]; + } + } + elem.className = jQuery.trim( setClass ); + } + } + } + } + + return this; + }, + + removeClass: function( value ) { + if ( jQuery.isFunction(value) ) { + return this.each(function(i) { + var self = jQuery(this); + self.removeClass( value.call(this, i, self.attr("class")) ); + }); + } + + if ( (value && typeof value === "string") || value === undefined ) { + var classNames = (value || "").split(rspace); + + for ( var i = 0, l = this.length; i < l; i++ ) { + var elem = this[i]; + + if ( elem.nodeType === 1 && elem.className ) { + if ( value ) { + var className = (" " + elem.className + " ").replace(rclass, " "); + for ( var c = 0, cl = classNames.length; c < cl; c++ ) { + className = className.replace(" " + classNames[c] + " ", " "); + } + elem.className = jQuery.trim( className ); + + } else { + elem.className = ""; + } + } + } + } + + return this; + }, + + toggleClass: function( value, stateVal ) { + var type = typeof value, isBool = typeof stateVal === "boolean"; + + if ( jQuery.isFunction( value ) ) { + return this.each(function(i) { + var self = jQuery(this); + self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); + }); + } + + return this.each(function() { + if ( type === "string" ) { + // toggle individual class names + var className, i = 0, self = jQuery(this), + state = stateVal, + classNames = value.split( rspace ); + + while ( (className = classNames[ i++ ]) ) { + // check each className given, space seperated list + state = isBool ? state : !self.hasClass( className ); + self[ state ? "addClass" : "removeClass" ]( className ); + } + + } else if ( type === "undefined" || type === "boolean" ) { + if ( this.className ) { + // store className if set + jQuery.data( this, "__className__", this.className ); + } + + // toggle whole className + this.className = this.className || value === false ? "" : jQuery.data( this, "__className__" ) || ""; + } + }); + }, + + hasClass: function( selector ) { + var className = " " + selector + " "; + for ( var i = 0, l = this.length; i < l; i++ ) { + if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { + return true; + } + } + + return false; + }, + + val: function( value ) { + if ( value === undefined ) { + var elem = this[0]; + + if ( elem ) { + if ( jQuery.nodeName( elem, "option" ) ) { + return (elem.attributes.value || {}).specified ? elem.value : elem.text; + } + + // We need to handle select boxes special + if ( jQuery.nodeName( elem, "select" ) ) { + var index = elem.selectedIndex, + values = [], + options = elem.options, + one = elem.type === "select-one"; + + // Nothing was selected + if ( index < 0 ) { + return null; + } + + // Loop through all the selected options + for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { + var option = options[ i ]; + + if ( option.selected ) { + // Get the specifc value for the option + value = jQuery(option).val(); + + // We don't need an array for one selects + if ( one ) { + return value; + } + + // Multi-Selects return an array + values.push( value ); + } + } + + return values; + } + + // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified + if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { + return elem.getAttribute("value") === null ? "on" : elem.value; + } + + + // Everything else, we just grab the value + return (elem.value || "").replace(rreturn, ""); + + } + + return undefined; + } + + var isFunction = jQuery.isFunction(value); + + return this.each(function(i) { + var self = jQuery(this), val = value; + + if ( this.nodeType !== 1 ) { + return; + } + + if ( isFunction ) { + val = value.call(this, i, self.val()); + } + + // Typecast each time if the value is a Function and the appended + // value is therefore different each time. + if ( typeof val === "number" ) { + val += ""; + } + + if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { + this.checked = jQuery.inArray( self.val(), val ) >= 0; + + } else if ( jQuery.nodeName( this, "select" ) ) { + var values = jQuery.makeArray(val); + + jQuery( "option", this ).each(function() { + this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; + }); + + if ( !values.length ) { + this.selectedIndex = -1; + } + + } else { + this.value = val; + } + }); + } +}); + +jQuery.extend({ + attrFn: { + val: true, + css: true, + html: true, + text: true, + data: true, + width: true, + height: true, + offset: true + }, + + attr: function( elem, name, value, pass ) { + // don't set attributes on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + if ( pass && name in jQuery.attrFn ) { + return jQuery(elem)[name](value); + } + + var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), + // Whether we are setting (or getting) + set = value !== undefined; + + // Try to normalize/fix the name + name = notxml && jQuery.props[ name ] || name; + + // Only do all the following if this is a node (faster for style) + if ( elem.nodeType === 1 ) { + // These attributes require special treatment + var special = rspecialurl.test( name ); + + // Safari mis-reports the default selected property of an option + // Accessing the parent's selectedIndex property fixes it + if ( name === "selected" && !jQuery.support.optSelected ) { + var parent = elem.parentNode; + if ( parent ) { + parent.selectedIndex; + + // Make sure that it also works with optgroups, see #5701 + if ( parent.parentNode ) { + parent.parentNode.selectedIndex; + } + } + } + + // If applicable, access the attribute via the DOM 0 way + if ( name in elem && notxml && !special ) { + if ( set ) { + // We can't allow the type property to be changed (since it causes problems in IE) + if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { + jQuery.error( "type property can't be changed" ); + } + + elem[ name ] = value; + } + + // browsers index elements by id/name on forms, give priority to attributes. + if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { + return elem.getAttributeNode( name ).nodeValue; + } + + // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set + // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ + if ( name === "tabIndex" ) { + var attributeNode = elem.getAttributeNode( "tabIndex" ); + + return attributeNode && attributeNode.specified ? + attributeNode.value : + rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? + 0 : + undefined; + } + + return elem[ name ]; + } + + if ( !jQuery.support.style && notxml && name === "style" ) { + if ( set ) { + elem.style.cssText = "" + value; + } + + return elem.style.cssText; + } + + if ( set ) { + // convert the value to a string (all browsers do this but IE) see #1070 + elem.setAttribute( name, "" + value ); + } + + var attr = !jQuery.support.hrefNormalized && notxml && special ? + // Some attributes require a special call on IE + elem.getAttribute( name, 2 ) : + elem.getAttribute( name ); + + // Non-existent attributes return null, we normalize to undefined + return attr === null ? undefined : attr; + } + + // elem is actually elem.style ... set the style + // Using attr for specific style information is now deprecated. Use style instead. + return jQuery.style( elem, name, value ); + } +}); +var rnamespaces = /\.(.*)$/, + fcleanup = function( nm ) { + return nm.replace(/[^\w\s\.\|`]/g, function( ch ) { + return "\\" + ch; + }); + }; + +/* + * A number of helper functions used for managing events. + * Many of the ideas behind this code originated from + * Dean Edwards' addEvent library. + */ +jQuery.event = { + + // Bind an event to an element + // Original by Dean Edwards + add: function( elem, types, handler, data ) { + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + // For whatever reason, IE has trouble passing the window object + // around, causing it to be cloned in the process + if ( elem.setInterval && ( elem !== window && !elem.frameElement ) ) { + elem = window; + } + + var handleObjIn, handleObj; + + if ( handler.handler ) { + handleObjIn = handler; + handler = handleObjIn.handler; + } + + // Make sure that the function being executed has a unique ID + if ( !handler.guid ) { + handler.guid = jQuery.guid++; + } + + // Init the element's event structure + var elemData = jQuery.data( elem ); + + // If no elemData is found then we must be trying to bind to one of the + // banned noData elements + if ( !elemData ) { + return; + } + + var events = elemData.events = elemData.events || {}, + eventHandle = elemData.handle, eventHandle; + + if ( !eventHandle ) { + elemData.handle = eventHandle = function() { + // Handle the second event of a trigger and when + // an event is called after a page has unloaded + return typeof jQuery !== "undefined" && !jQuery.event.triggered ? + jQuery.event.handle.apply( eventHandle.elem, arguments ) : + undefined; + }; + } + + // Add elem as a property of the handle function + // This is to prevent a memory leak with non-native events in IE. + eventHandle.elem = elem; + + // Handle multiple events separated by a space + // jQuery(...).bind("mouseover mouseout", fn); + types = types.split(" "); + + var type, i = 0, namespaces; + + while ( (type = types[ i++ ]) ) { + handleObj = handleObjIn ? + jQuery.extend({}, handleObjIn) : + { handler: handler, data: data }; + + // Namespaced event handlers + if ( type.indexOf(".") > -1 ) { + namespaces = type.split("."); + type = namespaces.shift(); + handleObj.namespace = namespaces.slice(0).sort().join("."); + + } else { + namespaces = []; + handleObj.namespace = ""; + } + + handleObj.type = type; + handleObj.guid = handler.guid; + + // Get the current list of functions bound to this event + var handlers = events[ type ], + special = jQuery.event.special[ type ] || {}; + + // Init the event handler queue + if ( !handlers ) { + handlers = events[ type ] = []; + + // Check for a special event handler + // Only use addEventListener/attachEvent if the special + // events handler returns false + if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { + // Bind the global event handler to the element + if ( elem.addEventListener ) { + elem.addEventListener( type, eventHandle, false ); + + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, eventHandle ); + } + } + } + + if ( special.add ) { + special.add.call( elem, handleObj ); + + if ( !handleObj.handler.guid ) { + handleObj.handler.guid = handler.guid; + } + } + + // Add the function to the element's handler list + handlers.push( handleObj ); + + // Keep track of which events have been used, for global triggering + jQuery.event.global[ type ] = true; + } + + // Nullify elem to prevent memory leaks in IE + elem = null; + }, + + global: {}, + + // Detach an event or set of events from an element + remove: function( elem, types, handler, pos ) { + // don't do events on text and comment nodes + if ( elem.nodeType === 3 || elem.nodeType === 8 ) { + return; + } + + var ret, type, fn, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, + elemData = jQuery.data( elem ), + events = elemData && elemData.events; + + if ( !elemData || !events ) { + return; + } + + // types is actually an event object here + if ( types && types.type ) { + handler = types.handler; + types = types.type; + } + + // Unbind all events for the element + if ( !types || typeof types === "string" && types.charAt(0) === "." ) { + types = types || ""; + + for ( type in events ) { + jQuery.event.remove( elem, type + types ); + } + + return; + } + + // Handle multiple events separated by a space + // jQuery(...).unbind("mouseover mouseout", fn); + types = types.split(" "); + + while ( (type = types[ i++ ]) ) { + origType = type; + handleObj = null; + all = type.indexOf(".") < 0; + namespaces = []; + + if ( !all ) { + // Namespaced event handlers + namespaces = type.split("."); + type = namespaces.shift(); + + namespace = new RegExp("(^|\\.)" + + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)") + } + + eventType = events[ type ]; + + if ( !eventType ) { + continue; + } + + if ( !handler ) { + for ( var j = 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( all || namespace.test( handleObj.namespace ) ) { + jQuery.event.remove( elem, origType, handleObj.handler, j ); + eventType.splice( j--, 1 ); + } + } + + continue; + } + + special = jQuery.event.special[ type ] || {}; + + for ( var j = pos || 0; j < eventType.length; j++ ) { + handleObj = eventType[ j ]; + + if ( handler.guid === handleObj.guid ) { + // remove the given handler for the given type + if ( all || namespace.test( handleObj.namespace ) ) { + if ( pos == null ) { + eventType.splice( j--, 1 ); + } + + if ( special.remove ) { + special.remove.call( elem, handleObj ); + } + } + + if ( pos != null ) { + break; + } + } + } + + // remove generic event handler if no more handlers exist + if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { + if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { + removeEvent( elem, type, elemData.handle ); + } + + ret = null; + delete events[ type ]; + } + } + + // Remove the expando if it's no longer used + if ( jQuery.isEmptyObject( events ) ) { + var handle = elemData.handle; + if ( handle ) { + handle.elem = null; + } + + delete elemData.events; + delete elemData.handle; + + if ( jQuery.isEmptyObject( elemData ) ) { + jQuery.removeData( elem ); + } + } + }, + + // bubbling is internal + trigger: function( event, data, elem /*, bubbling */ ) { + // Event object or event type + var type = event.type || event, + bubbling = arguments[3]; + + if ( !bubbling ) { + event = typeof event === "object" ? + // jQuery.Event object + event[expando] ? event : + // Object literal + jQuery.extend( jQuery.Event(type), event ) : + // Just the event type (string) + jQuery.Event(type); + + if ( type.indexOf("!") >= 0 ) { + event.type = type = type.slice(0, -1); + event.exclusive = true; + } + + // Handle a global trigger + if ( !elem ) { + // Don't bubble custom events when global (to avoid too much overhead) + event.stopPropagation(); + + // Only trigger if we've ever bound an event for it + if ( jQuery.event.global[ type ] ) { + jQuery.each( jQuery.cache, function() { + if ( this.events && this.events[type] ) { + jQuery.event.trigger( event, data, this.handle.elem ); + } + }); + } + } + + // Handle triggering a single element + + // don't do events on text and comment nodes + if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { + return undefined; + } + + // Clean up in case it is reused + event.result = undefined; + event.target = elem; + + // Clone the incoming data, if any + data = jQuery.makeArray( data ); + data.unshift( event ); + } + + event.currentTarget = elem; + + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery.data( elem, "handle" ); + if ( handle ) { + handle.apply( elem, data ); + } + + var parent = elem.parentNode || elem.ownerDocument; + + // Trigger an inline bound script + try { + if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { + if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { + event.result = false; + } + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( !event.isPropagationStopped() && parent ) { + jQuery.event.trigger( event, data, parent, true ); + + } else if ( !event.isDefaultPrevented() ) { + var target = event.target, old, + isClick = jQuery.nodeName(target, "a") && type === "click", + special = jQuery.event.special[ type ] || {}; + + if ( (!special._default || special._default.call( elem, event ) === false) && + !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { + + try { + if ( target[ type ] ) { + // Make sure that we don't accidentally re-trigger the onFOO events + old = target[ "on" + type ]; + + if ( old ) { + target[ "on" + type ] = null; + } + + jQuery.event.triggered = true; + target[ type ](); + } + + // prevent IE from throwing an error for some elements with some event types, see #3533 + } catch (e) {} + + if ( old ) { + target[ "on" + type ] = old; + } + + jQuery.event.triggered = false; + } + } + }, + + handle: function( event ) { + var all, handlers, namespaces, namespace, events; + + event = arguments[0] = jQuery.event.fix( event || window.event ); + event.currentTarget = this; + + // Namespaced event handlers + all = event.type.indexOf(".") < 0 && !event.exclusive; + + if ( !all ) { + namespaces = event.type.split("."); + event.type = namespaces.shift(); + namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"); + } + + var events = jQuery.data(this, "events"), handlers = events[ event.type ]; + + if ( events && handlers ) { + // Clone the handlers to prevent manipulation + handlers = handlers.slice(0); + + for ( var j = 0, l = handlers.length; j < l; j++ ) { + var handleObj = handlers[ j ]; + + // Filter the functions by class + if ( all || namespace.test( handleObj.namespace ) ) { + // Pass in a reference to the handler function itself + // So that we can later remove it + event.handler = handleObj.handler; + event.data = handleObj.data; + event.handleObj = handleObj; + + var ret = handleObj.handler.apply( this, arguments ); + + if ( ret !== undefined ) { + event.result = ret; + if ( ret === false ) { + event.preventDefault(); + event.stopPropagation(); + } + } + + if ( event.isImmediatePropagationStopped() ) { + break; + } + } + } + } + + return event.result; + }, + + props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), + + fix: function( event ) { + if ( event[ expando ] ) { + return event; + } + + // store a copy of the original event object + // and "clone" to set read-only properties + var originalEvent = event; + event = jQuery.Event( originalEvent ); + + for ( var i = this.props.length, prop; i; ) { + prop = this.props[ --i ]; + event[ prop ] = originalEvent[ prop ]; + } + + // Fix target property, if necessary + if ( !event.target ) { + event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either + } + + // check if target is a textnode (safari) + if ( event.target.nodeType === 3 ) { + event.target = event.target.parentNode; + } + + // Add relatedTarget, if necessary + if ( !event.relatedTarget && event.fromElement ) { + event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; + } + + // Calculate pageX/Y if missing and clientX/Y available + if ( event.pageX == null && event.clientX != null ) { + var doc = document.documentElement, body = document.body; + event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); + event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); + } + + // Add which for key events + if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) ) { + event.which = event.charCode || event.keyCode; + } + + // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) + if ( !event.metaKey && event.ctrlKey ) { + event.metaKey = event.ctrlKey; + } + + // Add which for click: 1 === left; 2 === middle; 3 === right + // Note: button is not normalized, so don't use it + if ( !event.which && event.button !== undefined ) { + event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); + } + + return event; + }, + + // Deprecated, use jQuery.guid instead + guid: 1E8, + + // Deprecated, use jQuery.proxy instead + proxy: jQuery.proxy, + + special: { + ready: { + // Make sure the ready event is setup + setup: jQuery.bindReady, + teardown: jQuery.noop + }, + + live: { + add: function( handleObj ) { + jQuery.event.add( this, handleObj.origType, jQuery.extend({}, handleObj, {handler: liveHandler}) ); + }, + + remove: function( handleObj ) { + var remove = true, + type = handleObj.origType.replace(rnamespaces, ""); + + jQuery.each( jQuery.data(this, "events").live || [], function() { + if ( type === this.origType.replace(rnamespaces, "") ) { + remove = false; + return false; + } + }); + + if ( remove ) { + jQuery.event.remove( this, handleObj.origType, liveHandler ); + } + } + + }, + + beforeunload: { + setup: function( data, namespaces, eventHandle ) { + // We only want to do this special case on windows + if ( this.setInterval ) { + this.onbeforeunload = eventHandle; + } + + return false; + }, + teardown: function( namespaces, eventHandle ) { + if ( this.onbeforeunload === eventHandle ) { + this.onbeforeunload = null; + } + } + } + } +}; + +var removeEvent = document.removeEventListener ? + function( elem, type, handle ) { + elem.removeEventListener( type, handle, false ); + } : + function( elem, type, handle ) { + elem.detachEvent( "on" + type, handle ); + }; + +jQuery.Event = function( src ) { + // Allow instantiation without the 'new' keyword + if ( !this.preventDefault ) { + return new jQuery.Event( src ); + } + + // Event object + if ( src && src.type ) { + this.originalEvent = src; + this.type = src.type; + // Event type + } else { + this.type = src; + } + + // timeStamp is buggy for some events on Firefox(#3843) + // So we won't rely on the native value + this.timeStamp = now(); + + // Mark it as fixed + this[ expando ] = true; +}; + +function returnFalse() { + return false; +} +function returnTrue() { + return true; +} + +// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding +// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html +jQuery.Event.prototype = { + preventDefault: function() { + this.isDefaultPrevented = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + + // if preventDefault exists run it on the original event + if ( e.preventDefault ) { + e.preventDefault(); + } + // otherwise set the returnValue property of the original event to false (IE) + e.returnValue = false; + }, + stopPropagation: function() { + this.isPropagationStopped = returnTrue; + + var e = this.originalEvent; + if ( !e ) { + return; + } + // if stopPropagation exists run it on the original event + if ( e.stopPropagation ) { + e.stopPropagation(); + } + // otherwise set the cancelBubble property of the original event to true (IE) + e.cancelBubble = true; + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = returnTrue; + this.stopPropagation(); + }, + isDefaultPrevented: returnFalse, + isPropagationStopped: returnFalse, + isImmediatePropagationStopped: returnFalse +}; + +// Checks if an event happened on an element within another element +// Used in jQuery.event.special.mouseenter and mouseleave handlers +var withinElement = function( event ) { + // Check if mouse(over|out) are still within the same parent element + var parent = event.relatedTarget; + + // Firefox sometimes assigns relatedTarget a XUL element + // which we cannot access the parentNode property of + try { + // Traverse up the tree + while ( parent && parent !== this ) { + parent = parent.parentNode; + } + + if ( parent !== this ) { + // set the correct event type + event.type = event.data; + + // handle event if we actually just moused on to a non sub-element + jQuery.event.handle.apply( this, arguments ); + } + + // assuming we've left the element since we most likely mousedover a xul element + } catch(e) { } +}, + +// In case of event delegation, we only need to rename the event.type, +// liveHandler will take care of the rest. +delegate = function( event ) { + event.type = event.data; + jQuery.event.handle.apply( this, arguments ); +}; + +// Create mouseenter and mouseleave events +jQuery.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" +}, function( orig, fix ) { + jQuery.event.special[ orig ] = { + setup: function( data ) { + jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); + }, + teardown: function( data ) { + jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); + } + }; +}); + +// submit delegation +if ( !jQuery.support.submitBubbles ) { + + jQuery.event.special.submit = { + setup: function( data, namespaces ) { + if ( this.nodeName.toLowerCase() !== "form" ) { + jQuery.event.add(this, "click.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { + return trigger( "submit", this, arguments ); + } + }); + + jQuery.event.add(this, "keypress.specialSubmit", function( e ) { + var elem = e.target, type = elem.type; + + if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { + return trigger( "submit", this, arguments ); + } + }); + + } else { + return false; + } + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialSubmit" ); + } + }; + +} + +// change delegation, happens here so we have bind. +if ( !jQuery.support.changeBubbles ) { + + var formElems = /textarea|input|select/i, + + changeFilters, + + getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if ( type === "radio" || type === "checkbox" ) { + val = elem.checked; + + } else if ( type === "select-multiple" ) { + val = elem.selectedIndex > -1 ? + jQuery.map( elem.options, function( elem ) { + return elem.selected; + }).join("-") : + ""; + + } else if ( elem.nodeName.toLowerCase() === "select" ) { + val = elem.selectedIndex; + } + + return val; + }, + + testChange = function testChange( e ) { + var elem = e.target, data, val; + + if ( !formElems.test( elem.nodeName ) || elem.readOnly ) { + return; + } + + data = jQuery.data( elem, "_change_data" ); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if ( e.type !== "focusout" || elem.type !== "radio" ) { + jQuery.data( elem, "_change_data", val ); + } + + if ( data === undefined || val === data ) { + return; + } + + if ( data != null || val ) { + e.type = "change"; + return jQuery.event.trigger( e, arguments[1], elem ); + } + }; + + jQuery.event.special.change = { + filters: { + focusout: testChange, + + click: function( e ) { + var elem = e.target, type = elem.type; + + if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { + return testChange.call( this, e ); + } + }, + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + keydown: function( e ) { + var elem = e.target, type = elem.type; + + if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" ) { + return testChange.call( this, e ); + } + }, + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information/focus[in] is not needed anymore + beforeactivate: function( e ) { + var elem = e.target; + jQuery.data( elem, "_change_data", getVal(elem) ); + } + }, + + setup: function( data, namespaces ) { + if ( this.type === "file" ) { + return false; + } + + for ( var type in changeFilters ) { + jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); + } + + return formElems.test( this.nodeName ); + }, + + teardown: function( namespaces ) { + jQuery.event.remove( this, ".specialChange" ); + + return formElems.test( this.nodeName ); + } + }; + + changeFilters = jQuery.event.special.change.filters; +} + +function trigger( type, elem, args ) { + args[0].type = type; + return jQuery.event.handle.apply( elem, args ); +} + +// Create "bubbling" focus and blur events +if ( document.addEventListener ) { + jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { + jQuery.event.special[ fix ] = { + setup: function() { + this.addEventListener( orig, handler, true ); + }, + teardown: function() { + this.removeEventListener( orig, handler, true ); + } + }; + + function handler( e ) { + e = jQuery.event.fix( e ); + e.type = fix; + return jQuery.event.handle.call( this, e ); + } + }); +} + +jQuery.each(["bind", "one"], function( i, name ) { + jQuery.fn[ name ] = function( type, data, fn ) { + // Handle object literals + if ( typeof type === "object" ) { + for ( var key in type ) { + this[ name ](key, data, type[key], fn); + } + return this; + } + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + var handler = name === "one" ? jQuery.proxy( fn, function( event ) { + jQuery( this ).unbind( event, handler ); + return fn.apply( this, arguments ); + }) : fn; + + if ( type === "unload" && name !== "one" ) { + this.one( type, data, fn ); + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.add( this[i], type, handler, data ); + } + } + + return this; + }; +}); + +jQuery.fn.extend({ + unbind: function( type, fn ) { + // Handle object literals + if ( typeof type === "object" && !type.preventDefault ) { + for ( var key in type ) { + this.unbind(key, type[key]); + } + + } else { + for ( var i = 0, l = this.length; i < l; i++ ) { + jQuery.event.remove( this[i], type, fn ); + } + } + + return this; + }, + + delegate: function( selector, types, data, fn ) { + return this.live( types, data, fn, selector ); + }, + + undelegate: function( selector, types, fn ) { + if ( arguments.length === 0 ) { + return this.unbind( "live" ); + + } else { + return this.die( types, null, fn, selector ); + } + }, + + trigger: function( type, data ) { + return this.each(function() { + jQuery.event.trigger( type, data, this ); + }); + }, + + triggerHandler: function( type, data ) { + if ( this[0] ) { + var event = jQuery.Event( type ); + event.preventDefault(); + event.stopPropagation(); + jQuery.event.trigger( event, data, this[0] ); + return event.result; + } + }, + + toggle: function( fn ) { + // Save reference to arguments for access in closure + var args = arguments, i = 1; + + // link all the functions, so any of them can unbind this click handler + while ( i < args.length ) { + jQuery.proxy( fn, args[ i++ ] ); + } + + return this.click( jQuery.proxy( fn, function( event ) { + // Figure out which function to execute + var lastToggle = ( jQuery.data( this, "lastToggle" + fn.guid ) || 0 ) % i; + jQuery.data( this, "lastToggle" + fn.guid, lastToggle + 1 ); + + // Make sure that clicks stop + event.preventDefault(); + + // and execute the function + return args[ lastToggle ].apply( this, arguments ) || false; + })); + }, + + hover: function( fnOver, fnOut ) { + return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); + } +}); + +var liveMap = { + focus: "focusin", + blur: "focusout", + mouseenter: "mouseover", + mouseleave: "mouseout" +}; + +jQuery.each(["live", "die"], function( i, name ) { + jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { + var type, i = 0, match, namespaces, preType, + selector = origSelector || this.selector, + context = origSelector ? this : jQuery( this.context ); + + if ( jQuery.isFunction( data ) ) { + fn = data; + data = undefined; + } + + types = (types || "").split(" "); + + while ( (type = types[ i++ ]) != null ) { + match = rnamespaces.exec( type ); + namespaces = ""; + + if ( match ) { + namespaces = match[0]; + type = type.replace( rnamespaces, "" ); + } + + if ( type === "hover" ) { + types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); + continue; + } + + preType = type; + + if ( type === "focus" || type === "blur" ) { + types.push( liveMap[ type ] + namespaces ); + type = type + namespaces; + + } else { + type = (liveMap[ type ] || type) + namespaces; + } + + if ( name === "live" ) { + // bind live handler + context.each(function(){ + jQuery.event.add( this, liveConvert( type, selector ), + { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); + }); + + } else { + // unbind live handler + context.unbind( liveConvert( type, selector ), fn ); + } + } + + return this; + } +}); + +function liveHandler( event ) { + var stop, elems = [], selectors = [], args = arguments, + related, match, handleObj, elem, j, i, l, data, + events = jQuery.data( this, "events" ); + + // Make sure we avoid non-left-click bubbling in Firefox (#3861) + if ( event.liveFired === this || !events || !events.live || event.button && event.type === "click" ) { + return; + } + + event.liveFired = this; + + var live = events.live.slice(0); + + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { + selectors.push( handleObj.selector ); + + } else { + live.splice( j--, 1 ); + } + } + + match = jQuery( event.target ).closest( selectors, event.currentTarget ); + + for ( i = 0, l = match.length; i < l; i++ ) { + for ( j = 0; j < live.length; j++ ) { + handleObj = live[j]; + + if ( match[i].selector === handleObj.selector ) { + elem = match[i].elem; + related = null; + + // Those two events require additional checking + if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { + related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; + } + + if ( !related || related !== elem ) { + elems.push({ elem: elem, handleObj: handleObj }); + } + } + } + } + + for ( i = 0, l = elems.length; i < l; i++ ) { + match = elems[i]; + event.currentTarget = match.elem; + event.data = match.handleObj.data; + event.handleObj = match.handleObj; + + if ( match.handleObj.origHandler.apply( match.elem, args ) === false ) { + stop = false; + break; + } + } + + return stop; +} + +function liveConvert( type, selector ) { + return "live." + (type && type !== "*" ? type + "." : "") + selector.replace(/\./g, "`").replace(/ /g, "&"); +} + +jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + + "change select submit keydown keypress keyup error").split(" "), function( i, name ) { + + // Handle event binding + jQuery.fn[ name ] = function( fn ) { + return fn ? this.bind( name, fn ) : this.trigger( name ); + }; + + if ( jQuery.attrFn ) { + jQuery.attrFn[ name ] = true; + } +}); + +// Prevent memory leaks in IE +// Window isn't included so as not to unbind existing unload events +// More info: +// - http://isaacschlueter.com/2006/10/msie-memory-leaks/ +if ( window.attachEvent && !window.addEventListener ) { + window.attachEvent("onunload", function() { + for ( var id in jQuery.cache ) { + if ( jQuery.cache[ id ].handle ) { + // Try/Catch is to handle iframes being unloaded, see #4280 + try { + jQuery.event.remove( jQuery.cache[ id ].handle.elem ); + } catch(e) {} + } + } + }); +} +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) { + selector += parts.shift(); + } + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + Sizzle.error( cur || selector ); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { + var filter = Expr.filter[ type ], found, item, left = match[1]; + anyFound = false; + + match.splice(1,1); + + if ( left.substr( left.length - 1 ) === "\\" ) { + continue; + } + + if ( curLoop === result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr === old ) { + if ( anyFound == null ) { + Sizzle.error( expr ); + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +Sizzle.error = function( msg ) { + throw "Syntax error, unrecognized expression: " + msg; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag ) { + part = part.toLowerCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = part.toLowerCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = part.toLowerCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { + if ( !inplace ) { + result.push( elem ); + } + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + return match[1].toLowerCase(); + }, + CHILD: function(match){ + if ( match[1] === "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 === i; + }, + eq: function(elem, i, match){ + return match[3] - 0 === i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } else { + Sizzle.error( "Syntax error, unrecognized expression: " + name ); + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + if ( type === "first" ) { + return true; + } + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) { + return false; + } + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first === 1 && last === 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first === 0 ) { + return diff === 0; + } else { + return ( diff % first === 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value !== check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, function(all, num){ + return "\\" + (num - 0 + 1); + })); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +// Also verifies that the returned array holds DOM nodes +// (which is not the case in the Blackberry browser) +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.compareDocumentPosition ? -1 : 1; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.sourceIndex ? -1 : 1; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return a.ownerDocument ? -1 : 1; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Utility function for retreiving the text value of an array of DOM nodes +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) { + (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE + })(); +} + +(function(){ + var div = document.createElement("div"); + + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + // Also, make sure that getElementsByClassName actually exists + if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { + return; + } + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) { + return; + } + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName.toLowerCase() === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return !!(a.compareDocumentPosition(b) & 16); +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + // documentElement is verified for cases where it doesn't yet exist + // (such as loading iframes in IE - #4833) + var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; + return documentElement ? documentElement.nodeName !== "HTML" : false; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE +jQuery.find = Sizzle; +jQuery.expr = Sizzle.selectors; +jQuery.expr[":"] = jQuery.expr.filters; +jQuery.unique = Sizzle.uniqueSort; +jQuery.text = getText; +jQuery.isXMLDoc = isXML; +jQuery.contains = contains; + +return; + +window.Sizzle = Sizzle; + +})(); +var runtil = /Until$/, + rparentsprev = /^(?:parents|prevUntil|prevAll)/, + // Note: This RegExp should be improved, or likely pulled from Sizzle + rmultiselector = /,/, + slice = Array.prototype.slice; + +// Implement the identical functionality for filter and not +var winnow = function( elements, qualifier, keep ) { + if ( jQuery.isFunction( qualifier ) ) { + return jQuery.grep(elements, function( elem, i ) { + return !!qualifier.call( elem, i, elem ) === keep; + }); + + } else if ( qualifier.nodeType ) { + return jQuery.grep(elements, function( elem, i ) { + return (elem === qualifier) === keep; + }); + + } else if ( typeof qualifier === "string" ) { + var filtered = jQuery.grep(elements, function( elem ) { + return elem.nodeType === 1; + }); + + if ( isSimple.test( qualifier ) ) { + return jQuery.filter(qualifier, filtered, !keep); + } else { + qualifier = jQuery.filter( qualifier, filtered ); + } + } + + return jQuery.grep(elements, function( elem, i ) { + return (jQuery.inArray( elem, qualifier ) >= 0) === keep; + }); +}; + +jQuery.fn.extend({ + find: function( selector ) { + var ret = this.pushStack( "", "find", selector ), length = 0; + + for ( var i = 0, l = this.length; i < l; i++ ) { + length = ret.length; + jQuery.find( selector, this[i], ret ); + + if ( i > 0 ) { + // Make sure that the results are unique + for ( var n = length; n < ret.length; n++ ) { + for ( var r = 0; r < length; r++ ) { + if ( ret[r] === ret[n] ) { + ret.splice(n--, 1); + break; + } + } + } + } + } + + return ret; + }, + + has: function( target ) { + var targets = jQuery( target ); + return this.filter(function() { + for ( var i = 0, l = targets.length; i < l; i++ ) { + if ( jQuery.contains( this, targets[i] ) ) { + return true; + } + } + }); + }, + + not: function( selector ) { + return this.pushStack( winnow(this, selector, false), "not", selector); + }, + + filter: function( selector ) { + return this.pushStack( winnow(this, selector, true), "filter", selector ); + }, + + is: function( selector ) { + return !!selector && jQuery.filter( selector, this ).length > 0; + }, + + closest: function( selectors, context ) { + if ( jQuery.isArray( selectors ) ) { + var ret = [], cur = this[0], match, matches = {}, selector; + + if ( cur && selectors.length ) { + for ( var i = 0, l = selectors.length; i < l; i++ ) { + selector = selectors[i]; + + if ( !matches[selector] ) { + matches[selector] = jQuery.expr.match.POS.test( selector ) ? + jQuery( selector, context || this.context ) : + selector; + } + } + + while ( cur && cur.ownerDocument && cur !== context ) { + for ( selector in matches ) { + match = matches[selector]; + + if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { + ret.push({ selector: selector, elem: cur }); + delete matches[selector]; + } + } + cur = cur.parentNode; + } + } + + return ret; + } + + var pos = jQuery.expr.match.POS.test( selectors ) ? + jQuery( selectors, context || this.context ) : null; + + return this.map(function( i, cur ) { + while ( cur && cur.ownerDocument && cur !== context ) { + if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selectors) ) { + return cur; + } + cur = cur.parentNode; + } + return null; + }); + }, + + // Determine the position of an element within + // the matched set of elements + index: function( elem ) { + if ( !elem || typeof elem === "string" ) { + return jQuery.inArray( this[0], + // If it receives a string, the selector is used + // If it receives nothing, the siblings are used + elem ? jQuery( elem ) : this.parent().children() ); + } + // Locate the position of the desired element + return jQuery.inArray( + // If it receives a jQuery object, the first element is used + elem.jquery ? elem[0] : elem, this ); + }, + + add: function( selector, context ) { + var set = typeof selector === "string" ? + jQuery( selector, context || this.context ) : + jQuery.makeArray( selector ), + all = jQuery.merge( this.get(), set ); + + return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? + all : + jQuery.unique( all ) ); + }, + + andSelf: function() { + return this.add( this.prevObject ); + } +}); + +// A painfully simple check to see if an element is disconnected +// from a document (should be improved, where feasible). +function isDisconnected( node ) { + return !node || !node.parentNode || node.parentNode.nodeType === 11; +} + +jQuery.each({ + parent: function( elem ) { + var parent = elem.parentNode; + return parent && parent.nodeType !== 11 ? parent : null; + }, + parents: function( elem ) { + return jQuery.dir( elem, "parentNode" ); + }, + parentsUntil: function( elem, i, until ) { + return jQuery.dir( elem, "parentNode", until ); + }, + next: function( elem ) { + return jQuery.nth( elem, 2, "nextSibling" ); + }, + prev: function( elem ) { + return jQuery.nth( elem, 2, "previousSibling" ); + }, + nextAll: function( elem ) { + return jQuery.dir( elem, "nextSibling" ); + }, + prevAll: function( elem ) { + return jQuery.dir( elem, "previousSibling" ); + }, + nextUntil: function( elem, i, until ) { + return jQuery.dir( elem, "nextSibling", until ); + }, + prevUntil: function( elem, i, until ) { + return jQuery.dir( elem, "previousSibling", until ); + }, + siblings: function( elem ) { + return jQuery.sibling( elem.parentNode.firstChild, elem ); + }, + children: function( elem ) { + return jQuery.sibling( elem.firstChild ); + }, + contents: function( elem ) { + return jQuery.nodeName( elem, "iframe" ) ? + elem.contentDocument || elem.contentWindow.document : + jQuery.makeArray( elem.childNodes ); + } +}, function( name, fn ) { + jQuery.fn[ name ] = function( until, selector ) { + var ret = jQuery.map( this, fn, until ); + + if ( !runtil.test( name ) ) { + selector = until; + } + + if ( selector && typeof selector === "string" ) { + ret = jQuery.filter( selector, ret ); + } + + ret = this.length > 1 ? jQuery.unique( ret ) : ret; + + if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { + ret = ret.reverse(); + } + + return this.pushStack( ret, name, slice.call(arguments).join(",") ); + }; +}); + +jQuery.extend({ + filter: function( expr, elems, not ) { + if ( not ) { + expr = ":not(" + expr + ")"; + } + + return jQuery.find.matches(expr, elems); + }, + + dir: function( elem, dir, until ) { + var matched = [], cur = elem[dir]; + while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { + if ( cur.nodeType === 1 ) { + matched.push( cur ); + } + cur = cur[dir]; + } + return matched; + }, + + nth: function( cur, result, dir, elem ) { + result = result || 1; + var num = 0; + + for ( ; cur; cur = cur[dir] ) { + if ( cur.nodeType === 1 && ++num === result ) { + break; + } + } + + return cur; + }, + + sibling: function( n, elem ) { + var r = []; + + for ( ; n; n = n.nextSibling ) { + if ( n.nodeType === 1 && n !== elem ) { + r.push( n ); + } + } + + return r; + } +}); +var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, + rleadingWhitespace = /^\s+/, + rxhtmlTag = /(<([\w:]+)[^>]*?)\/>/g, + rselfClosing = /^(?:area|br|col|embed|hr|img|input|link|meta|param)$/i, + rtagName = /<([\w:]+)/, + rtbody = /"; + }, + wrapMap = { + option: [ 1, "" ], + legend: [ 1, "
", "
" ], + thead: [ 1, "", "
" ], + tr: [ 2, "", "
" ], + td: [ 3, "", "
" ], + col: [ 2, "", "
" ], + area: [ 1, "", "" ], + _default: [ 0, "", "" ] + }; + +wrapMap.optgroup = wrapMap.option; +wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; +wrapMap.th = wrapMap.td; + +// IE can't serialize and + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Moving, Rotating, Mirroring, and Scaling

+

Next topic

+

Gathering Information and Miscellaneous Methods

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Boolean Operations¶

+

Boolean operations operate on two solids. The following shapes are +used in the following examples:

+
s1 = ccad.sphere(1.0)
+s2 = ccad.box(1.0, 2.0, 3.0)
+
+
+

s3 is the derived shape.

+
+

Fuse¶

+

fuse fuses two solids together. Either one of the two lines below +fuses s1 and s2:

+
s3 = s1 + s2
+
+
+

or:

+
s3 = ccad.fuse(s1, s2)
+
+
+_images/boolean_fuse.png +
+
+

Cut¶

+

cut cuts one solid from the other. Either one of the two lines below +cuts s2 from s1:

+
s3 = s1 - s2
+
+
+

or:

+
s3 = ccad.cut(s1, s2)
+
+
+_images/boolean_cut.png +
+
+

Common¶

+

common finds the common portions of each solid. Either one of the two +lines below finds the common portions of s1 and s2:

+
s3 = s1 & s2
+
+
+

or:

+
s3 = ccad.common(s1, s2)
+
+
+_images/boolean_common.png +
+
+

Glue¶

+

Boolean operations can have trouble when two solids have coincident +faces. Sometimes the glue function can help. glue merges two solids +at a coincident face. Unfortunately, you need to know the face +indices that must merge. Read more about finding face indices in the +Display or Logging sections:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s2 = ccad.box(1.0, 1.0, 1.0)
+s2.translate((1.0, 0.5, 1.0))
+s3 = ccad.glue(s1, s2, [(1, 0)])
+
+
+_images/boolean_glue.png +

A more robust glue called simple_glue exists in the API, which +isn’t part of OCC. For this form, however, the faces to be glued must +exactly overlap.

+
+
+

Notes¶

+

Functions to use OCC’s old fuse, cut, and common algorithms, and +functions to perform a fillet or chamfer on the newly created edges +after a boolean operation are also available. I use them rarely. See +the API.

+

Sometimes OCC has trouble with booleans. I found the following helpful:

+
+
    +
  • Eliminate unneeded edges. OCC’s boolean operations often return +two faces in the same domain with an edge between them that can be +merged. Eliminating these edges by merging the faces help +subsequent boolean operations. The simplify method can do this +for some shapes.
  • +
  • Move problem edges out of the way. Cylinder and sphere edges are +necessary in OCC, but their position can often be rotated away +from a problem boolean location.
  • +
  • Slice the object (with a box or something) into sections. Then +fuse those sections back together. Then apply the boolean +operation. This was particularly helpful for creating pockets in +a solid.
  • +
+
+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/configuring_display.html b/src/contrib/ccad/doc/html/configuring_display.html new file mode 100644 index 000000000..a18fe24a8 --- /dev/null +++ b/src/contrib/ccad/doc/html/configuring_display.html @@ -0,0 +1,145 @@ + + + + + + + Configuring Display — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Controlling Display

+

Next topic

+

Example - Building Toy

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Configuring Display¶

+

The viewer has a few simple configuring options to customize it for +your application.

+
+

add_menuitem¶

+

You may add another pulldown menu item with add_menuitem. The +following example adds the item Car1 under the Model menu:

+
v1.add_menuitem(('Model', 'Car1'), display_car, 1)
+v1.add_menuitem(('Model', 'Car2'), display_car, 2)
+
+
+

The first argument is a tuple that defines the menu hierarchy. The +first item in the tuple is the pull-down menu title. The last item is +the menu item. Intermediate items are submenus. If a menu doesn’t +exist, it is created. display_car is the function to call when the +item is selected. Pass parameters follow the function call. +display_car might look like this:

+
def display_car(widget, version):
+    ...
+
+
+
+
+

add_menu¶

+

If you know gtk and you want menu items fancier than strings, +add_menu, a lower-level menu manipulation method is available. See +the API for details.

+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/contents.html b/src/contrib/ccad/doc/html/contents.html new file mode 100644 index 000000000..af4da0f1c --- /dev/null +++ b/src/contrib/ccad/doc/html/contents.html @@ -0,0 +1,137 @@ + + + + + + + Contents — ccad v0.1 documentation + + + + + + + + + + +
+
+

Next topic

+

Introduction

+

This Page

+ + + +
+
+ + + + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/controlling_display.html b/src/contrib/ccad/doc/html/controlling_display.html new file mode 100644 index 000000000..d5553e099 --- /dev/null +++ b/src/contrib/ccad/doc/html/controlling_display.html @@ -0,0 +1,237 @@ + + + + + + + Controlling Display — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Using Display

+

Next topic

+

Configuring Display

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Controlling Display¶

+

The viewer has a number of methods that help control the display from +within your programs.

+
+

viewstandard¶

+

The viewing projection can be set to a standard projection with +viewstandard. The following example sets the view to front:

+
v1.viewstandard(viewtype = 'front')
+
+
+
+
+

set_projection¶

+

The viewing projection can be set to any arbitrary projection with +set_projection. To look at the point (0.0, 0.0, 0.0) from the z-direction with the y-direction up, I could call:

+
v1.set_projection((0.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, 1.0, 0.0))
+
+
+
+
+

set_scale¶

+

set_scale scales the viewing window so the entire screen spans a +certain number of scene units. The following example sets the scale +such that a shape 8.0 long would span the screen:

+
v1.set_scale(8.0)
+
+
+
+
+

set_size¶

+

set_size sets the size of the viewing window in pixels. The following example sets the viewing window to be 600 by 800 pixels:

+
v1.set_size((600, 800))
+
+
+
+
+

set_background¶

+

set_background sets the background color. The following example sets +the background color to white:

+
v1.set_background((1.0, 1.0, 1.0))
+
+
+
+
+

set_foreground¶

+

set_foreground sets the default shape color. The following example +sets the foreground color to red:

+
v1.set_foreground((1.0, 0.0, 0.0))
+
+
+
+
+

set_triedron¶

+

set_triedron sets how the triedron (the little x, y, z coordinate +sprite) is displayed.

+
+
+

display¶

+

The examples have already used display extensively. However, there +are options to control color, material, transparency, line type, and +line width. The following example draws s1 red, in a metallized +material, halfway transparent:

+
s1 = ccad.sphere(1.0) + ccad.box(1.0, 2.0, 2.0)
+v1.display(s1, color = (1.0, 0.0, 0.0), material = 'metallized', transparency = 0.5)
+
+
+_images/display_display.png +
+
+

fit¶

+

fits the scene to the window.

+
+
+

clear¶

+

clear clears the window.

+
+
+

save¶

+

save saves a screenshot. The following example saves the screen to +the file, ‘screendump.png’:

+
v1.save('screendump.png')
+
+
+
+
+

mode_wireframe¶

+

mode_wireframe sets the window to wireframe display.

+
+
+

mode_shaded¶

+

mode_shaded sets the window to shaded display.

+
+
+

mode_hlr¶

+

mode_hlr sets the window to hidden line removal display.

+
+
+

mode_drawing¶

+

mode_drawing sets the window to a drafting-like drawing display by +using a custom hidden line removal mode. After calling it, call the +reset_mode_drawing method to restore regular control.

+
+
+

quit¶

+

quit closes the window.

+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/creating_derived.html b/src/contrib/ccad/doc/html/creating_derived.html new file mode 100644 index 000000000..9db1bf640 --- /dev/null +++ b/src/contrib/ccad/doc/html/creating_derived.html @@ -0,0 +1,244 @@ + + + + + + + Creating Derived Shapes — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Creating Solids

+

Next topic

+

Moving, Rotating, Mirroring, and Scaling

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Creating Derived Shapes¶

+

Some shapes are derived from others and create arbitrary shapes, +depending on what’s passed to them. They follow.

+
+

prism¶

+

A prism extrudes a shape in a given linear direction. A vertex is +extruded as an edge; an edge is extruded as a face; a wire is extruded +as a shell; and a face is extruded as a solid. The following example +creates an extrusion of a hexagon:

+
f1 = ccad.plane(ccad.ngon(2.0, 6))
+s1 = ccad.prism(f1, (0.0, 0.0, 1.0))
+
+
+_images/derived_prism.png +
+
+

revol¶

+

A revol extrudes a shape in a circle. A vertex is extruded as an +edge; an edge is extruded as a face; a wire is extruded as a shell; +and a face is extruded as a solid. The following example extrudes a +squared circle about (0.0, 0.0, 0.0):

+
e1 = ccad.arc(1.0, -math.pi/2, math.pi/2)
+e1.translate((3.0, 0.0, 0.0))
+w1 = ccad.polygon([(3.0, 1.0, 0.0),
+                   (2.0, 1.0, 0.0),
+                   (2.0, -1.0, 0.0),
+                   (3.0, -1.0, 0.0)])
+f1 = ccad.plane(ccad.wire([e1, w1]))
+s1 = ccad.revol(f1, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), 2*math.pi)
+
+
+_images/derived_revol.png +
+
+

loft¶

+

A loft returns a solid or shell that fits a list of closed wires. The +following example fits a changing-diameter circle:

+
w1 = ccad.wire([ccad.circle(1.0)])
+w2 = ccad.wire([ccad.circle(2.0)])
+w2.translate((0.0, 0.0, 1.0))
+w3 = ccad.wire([ccad.circle(1.5)])
+w3.translate((0.0, 0.0, 2.0))
+s1 = ccad.loft([w1, w2, w3], stype = 'solid')
+
+
+_images/derived_loft.png +
+
+

pipe¶

+

A pipe returns a solid or shell that is an extrusion of a closed wire +profile along a wire spine. The following example extrudes a hexagon +along an arching and then straight spine:

+
profile = ccad.ngon(2.0, 6)
+e1 = ccad.arc(8.0, 0.0, math.pi/2)
+e2 = ccad.segment((0.0, 8.0, 0.0), (-8.0, 8.0, 0.0))
+spine = ccad.wire([e1, e2])
+spine.translate((-8.0, 0.0, 0.0))
+spine.rotatex(math.pi/2)
+s1 = ccad.pipe(profile, spine)
+
+
+_images/derived_pipe.png +
+
+

helical_solid¶

+

A helical_solid is a pipe spun on a helical spine. Unfortunately, +pipe had trouble with the profile’s orientation on a helix, so +helical_solid was needed. The following example extrudes a triangle +along a helix:

+
profile = ccad.ngon(0.2, 3)
+s1 = ccad.helical_solid(profile, 2.0, 1.0/math.pi, 2)
+
+
+_images/derived_helical_solid.png +
+
+

offset¶

+

offset offsets a face or solid by a given distance and returns a list +of faces or solids. Why doesn’t it return a single face or solid? +Depending on the distance, offsetting can create multiple shapes from +one. For example, consider a 2D version of an octopus. As offset +gets larger and larger, parts of the face merge and can create holes. +The following example offsets a hexagon by 1.0. In this example, the +returned list is only one item long:

+
w1 = ccad.ngon(8.0, 6)
+w2 = ccad.offset(ccad.plane(w1), 1.0)[0]
+
+
+_images/derived_offset_face.png +

The following example offsets a box with a hole by 1.0. In this +example, the returned list is only one item long:

+
b1 = ccad.box(10.0, 10.0, 10.0)
+b1.translate((-5.0, -5.0, 0.0))
+c1 = ccad.cylinder(2.5, 20.0)
+c1.translate((0.0, 0.0, -5.0))
+s1 = b1 - c1
+s2 = ccad.offset(s1, 1.0)[0]
+
+
+_images/derived_offset_solid.png +

Note the offset shape has edges rounded. Currently, this is the only +option OCC provides.

+

A positive value for offset makes the new face or solid outside the +original. A negative value makes the new face or solid inside the +original.

+
+
+

slice¶

+

slice slices a solid with a plane and returns a list of faces at the +intersection of the solid and plane. The plane can be passed or a +value for x, y, or z can be given which generates the plane. The +following example slices a box with a hole at z = 1.0:

+
b1 = ccad.box(10.0, 10.0, 10.0)
+b1.translate((-5.0, -5.0, 0.0))
+c1 = ccad.cylinder(2.5, 20.0)
+c1.translate((0.0, 0.0, -5.0))
+s1 = b1 - c1
+f1 = ccad.slice(s1, z = 1.0)[0]
+
+
+_images/derived_slice.png +
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/creating_edges.html b/src/contrib/ccad/doc/html/creating_edges.html new file mode 100644 index 000000000..d4c71ce95 --- /dev/null +++ b/src/contrib/ccad/doc/html/creating_edges.html @@ -0,0 +1,201 @@ + + + + + + + Creating Edges — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Modelling

+

Next topic

+

Creating Wires

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Creating Edges¶

+

Functions that instantiate an edge follow. In each example, the edge +is called e1.

+
+

segment¶

+

A segment defines a straight line from one point to another. The +following example creates a segment from the origin to the point +(1.0, 0.0, 0.0):

+
pt1 = (0.0, 0.0, 0.0)
+pt2 = (1.0, 0.0, 0.0)
+e1 = ccad.segment(pt1, pt2)
+
+
+_images/edge_segment.png +
+
+

arc¶

+

An arc defines a portion of a circle. The following example creates +an arc centered on the origin of radius 1.0 that extends from 0.0 +radians to pi/2 radians:

+
e1 = ccad.arc(1.0, 0.0, math.pi/2)
+
+
+_images/edge_arc.png +
+
+

arc_ellipse¶

+

An arc_ellipse defines a portion of an ellipse. The following example +creates an arc_ellipse centered on the origin with major radius 2.0, +minor radius 1.0, and that extends from 0.0 radians to pi/2 radians:

+
e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2)
+
+
+_images/edge_arc_ellipse.png +
+
+

spline¶

+

A spline defines a 3D spline fit to a series of passed points. The +following example creates a spline that passes through the points +(0.0, 0.0, 0.0), (0.2, 0.1, 0.0), (0.5, 0.2, 0.0), (-0.5, 0.3, 0.0):

+
e1 = ccad.spline([(0.0, 0.0, 0.0),
+                  (0.2, 0.1, 0.0),
+                  (0.5, 0.2, 0.0),
+                  (-0.5, 0.3, 0.0)])
+
+
+_images/edge_spline.png +
+
+

bezier¶

+

A bezier edge is an edge that passes from and to the endpoints. The +intermediate points shape the curve. The following example creates a +bezier that is the same as a quarter circle:

+
e1 = ccad.bezier([(1.0, 0.0, 0.0),
+                  (1.0, 1.0, 0.0),
+                  (0.0, 1.0, 0.0)], [1.0, 1.0/math.sqrt(2.0), 1.0])
+
+
+_images/edge_bezier.png +

It can also be called without weights, which will return the curves +used in svg files.

+
+
+

circle¶

+

The following example creates a circle centered on the origin with +radius 3.0:

+
e1 = ccad.circle(3.0)
+
+
+_images/edge_circle.png +
+
+

ellipse¶

+

The following example creates an ellipse centered on the origin with major radius 2.0 and minor radius 1.0:

+
e1 = ccad.ellipse(2.0, 1.0)
+
+
+_images/edge_ellipse.png +
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/creating_faces.html b/src/contrib/ccad/doc/html/creating_faces.html new file mode 100644 index 000000000..1e0d1f6b4 --- /dev/null +++ b/src/contrib/ccad/doc/html/creating_faces.html @@ -0,0 +1,164 @@ + + + + + + + Creating Faces — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Creating Wires

+

Next topic

+

Creating Solids

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Creating Faces¶

+

Functions that instantiate a face follow. In each example, the face +is called f1.

+
+

plane¶

+

A plane is a flat shape bounded by a closed wire in 3D space. The +wire must be planar (flat) to work. The following example creates a +face inside an ngon:

+
w1 = ccad.ngon(2.0, 5)
+f1 = ccad.plane(w1)
+
+
+_images/face_plane.png +
+
+

face_from¶

+

face_from creates a face that mathematically exactly matches another +face but is bounded by a different wire. The following example +creates a filled square from a filled octagon. The example isn’t very +practical. face_from is more often used when the face is 3D and you +want to bound it by a different wire:

+
w1 = ccad.ngon(2.0, 8)
+w2 = ccad.ngon(10.0, 4)
+f2 = ccad.plane(w1)
+f1 = ccad.face_from(f2, w2)
+
+
+_images/face_face_from.png +
+
+

filling¶

+

A filling is a surface fit to a wire, and that wire does not need to +be planar. The following example fits a surface to a wavy wire. Both +the wire and face are shown:

+
w1 = ccad.spline([(0.0, 0.0, 0.0),
+                  (1.0, 0.2, 0.3),
+                  (1.5, 0.8, 1.0),
+                  (0.8, 1.2, 0.2),
+                  (-0.5, 0.8, -0.5),
+                  (0.0, 0.0, 0.0)])
+f1 = ccad.filling(w1)
+
+
+_images/face_filling.png +

Visit the API documentation for more filling options.

+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/creating_solids.html b/src/contrib/ccad/doc/html/creating_solids.html new file mode 100644 index 000000000..883d20507 --- /dev/null +++ b/src/contrib/ccad/doc/html/creating_solids.html @@ -0,0 +1,187 @@ + + + + + + + Creating Solids — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Creating Faces

+

Next topic

+

Creating Derived Shapes

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Creating Solids¶

+

Functions that instantiate a solid follow. In each example, the solid +is called s1.

+
+

box¶

+

A box is a six-sided perpendicular-faced solid. The following example +creates a box that extends 1.0 in the x-direction, 2.0 in the +y-direction, and 3.0 in the z-direction:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+
+
+_images/solid_box.png +
+
+

wedge¶

+

A wedge is a box with one or more tapered sides. The following +example creates a wedge that extends 2.0 in the y-direction and 3.0 in +the z-direction and tapers from 1.0 to 0.5 in the x-direction:

+
s1 = ccad.wedge(1.0, 2.0, 3.0, 0.5)
+
+
+_images/solid_wedge.png +
+
+

cylinder¶

+

The following example creates a cylinder 1.0 in radius and 2.0 high:

+
s1 = ccad.cylinder(1.0, 2.0)
+
+
+_images/solid_cylinder.png +
+
+

sphere¶

+

The following example creates a sphere 5.0 in radius:

+
s1 = ccad.sphere(5.0)
+
+
+_images/solid_sphere.png +
+
+

cone¶

+

The following example creates the bottom of a cone, radius 4.0 at the +bottom, 2.0 at the top, and 2.0 high:

+
s1 = ccad.cone(4.0, 2.0, 2.0)
+
+
+_images/solid_cone.png +

A similar function, bezier_cone, returns an identical solid, but the +cone face is a surface of revolution. Fillets worked better with it +sometimes.

+
+
+

torus¶

+

The following example creates a torus 10.0 from torus center to +extruded circle center, and 1.0 in radius:

+
s1 = ccad.torus(10.0, 1.0)
+
+
+_images/solid_torus.png +
+
+

Notes¶

+

Many of these functions offer more options than shown. Consult the +API for details.

+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/creating_wires.html b/src/contrib/ccad/doc/html/creating_wires.html new file mode 100644 index 000000000..e900e348f --- /dev/null +++ b/src/contrib/ccad/doc/html/creating_wires.html @@ -0,0 +1,182 @@ + + + + + + + Creating Wires — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Creating Edges

+

Next topic

+

Creating Faces

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Creating Wires¶

+

Functions that instantiate a wire follow. In each example, the wire +is called w1.

+
+

Connection of Edges¶

+

wire is one of the few classes you’ll instantiate directly in ccad. +Often, you’ll create a wire manually by connecting multiple edges. Do +it by passing a list of edges to a wire instantiation. The following +example creates an elongated circle:

+
e1 = ccad.arc(1.0, -math.pi/2, math.pi/2)
+e1.translate((1.0, 0.0, 0.0))
+e2 = ccad.segment((1.0, 1.0, 0.0), (-1.0, 1.0, 0.0))
+e3 = ccad.arc(1.0, math.pi/2, 3*math.pi/2)
+e3.translate((-1.0, 0.0, 0.0))
+e4 = ccad.segment((-1.0, -1.0, 0.0), (1.0, -1.0, 0.0))
+w1 = cm.wire([e1, e2, e3, e4])
+
+
+_images/wire.png +

You’ll learn more about translate later.

+
+
+

polygon¶

+

A polygon defines a connection of segments. To close the polygon, +make the last point equal to the first. The following example creates +a polygon:

+
w1 = ccad.polygon([(0.0, 0.0, 0.0),
+                   (1.0, 0.0, 0.0),
+                   (1.5, 1.0, 0.0),
+                   (0.5, 1.5, 0.0),
+                   (-0.5, -0.5, 0.0)])
+
+
+_images/wire_polygon.png +
+
+

rectangle¶

+

Create a rectangle by passing the x-distance and y-distance. The following example creates a rectangle 2.0 wide and 1.0 high. The lower-left corner is at (0.0, 0.0, 0.0):

+
w1 = ccad.rectangle(2.0, 1.0)
+
+
+_images/wire_rectangle.png +
+
+

ngon¶

+

An ngon defines an equal-sided polygon with n segments. The following +example creates a hexagon with radius (distance from center to vertex) +of 2.0:

+
w1 = ccad.ngon(2.0, 6)
+
+
+_images/wire_ngon.png +
+
+

helix¶

+

The following example creates a helix with radius 2.0, helical angle +of 1.0/pi, and 3 turns:

+
w1 = ccad.helix(2.0, 1.0/math.pi, 3)
+
+
+_images/wire_helix.png +
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/details.html b/src/contrib/ccad/doc/html/details.html new file mode 100644 index 000000000..900db0d13 --- /dev/null +++ b/src/contrib/ccad/doc/html/details.html @@ -0,0 +1,164 @@ + + + + + + + Details — ccad v0.1 documentation + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Introduction

+

Next topic

+

Modelling

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Details¶

+
+

Internals¶

+

ccad is a python wrapper of pythonocc, which is a python wrapper of +OpenCascade, an open source mechanical CAD math engine. So why wrap +python with more python? pythonocc is a SWIG wrapper of a C++ +project. Because of that, the syntax is fairly cumbersome. The +sphere instance from the introduction page looks like this in +pythonocc:

+
from OCC.BRepPrimAPI import *
+s1 = BRepPrimAPI_Sphere(10.0).Shape()
+
+
+

ccad syntax is simpler, and allows you to focus more on design and +less on syntax. Put simply, pythonocc is more for CAD developers; +ccad is more for CAD users.

+

Unfortunately, ccad use comes with a cost. Not all of pythonocc’s +abilities are yet wrapped. Therefore, it lacks the power of +pythonocc.

+

However, extending ccad isn’t too hard. You can extend ccad with +python calls to pythoncc. No C or C++ coding is necessary. We’re +always looking for pythonocc-skilled people to extend ccad’s +abilities.

+
+
+

User Requirements¶

+

You’ll need to know python reasonably well to use ccad. Additionally, +the pydoc generated documentation adds further detail to this +manual. Keep it handly.

+
+
+

System Requirements¶

+

You’ll need pythonocc, python-gtk, and python-gtkglext to run ccad.

+
+
+

Installation¶

+

To install ccad, follow the following procedure in Linux:

+
tar xvzf ccad-ver.tar.gz (where ver is the version number)
+cd ccad-ver (where ver is the version number)
+python setup.py install --prefix=/usr/local (as root)
+
+

Change the prefix argument to install in a different directory.

+

If you’re a Windows or Mac user, ccad should still work. I just +haven’t tried it. If you successfully install ccad in Windows or Mac, +let us know how you did it, and we’ll update the documentation.

+

If you’re having trouble, simply extract the .tar.gz file. Then, add +that directory to your PYTHONPATH. That, at least, will get you going.

+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/displaying.html b/src/contrib/ccad/doc/html/displaying.html new file mode 100644 index 000000000..b0f40020e --- /dev/null +++ b/src/contrib/ccad/doc/html/displaying.html @@ -0,0 +1,151 @@ + + + + + + + Displaying — ccad v0.1 documentation + + + + + + + + + + + +
+
+

Previous topic

+

File Transfer

+

Next topic

+

Stand-Alone Viewing

+

This Page

+ + + +
+
+ + + + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/example1.html b/src/contrib/ccad/doc/html/example1.html new file mode 100644 index 000000000..4b3a53dfb --- /dev/null +++ b/src/contrib/ccad/doc/html/example1.html @@ -0,0 +1,135 @@ + + + + + + + Example - Building Toy — ccad v0.1 documentation + + + + + + + + + + + +
+
+

Previous topic

+

Configuring Display

+

Next topic

+

A Single Brick

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Example - Building Toy¶

+

Suppose I want to generate a brick building toy for plastic injection +molding, similar to the one we know and love. I’m looking for bricks +with the following unit configurations all the same height.

+
    +
  • 1 x 1
  • +
  • 2 x 1
  • +
  • 4 x 1
  • +
  • 6 x 1
  • +
  • 8 x 1
  • +
  • 2 x 2
  • +
  • 4 x 2
  • +
  • 8 x 2
  • +
  • 4 x 4
  • +
+

10mm is the brick height, and a unit brick will be 5mm by 5mm in x and +y. I’ll use ipython to develop and modify. When I’m pleased with a +block of code, I’ll place it in a file for future use. I won’t show +the ipython prompt.

+ +
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/example1_brickpy.html b/src/contrib/ccad/doc/html/example1_brickpy.html new file mode 100644 index 000000000..52caab606 --- /dev/null +++ b/src/contrib/ccad/doc/html/example1_brickpy.html @@ -0,0 +1,200 @@ + + + + + + + brick.py — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Previous topic

+

A Single Brick

+

Next topic

+

Multiple Bricks

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

brick.py¶

+

Now that I’m satisfied with my brick, I’ll place the ipython code in a +function in brick.py. I’ll call the function brick and pass xsize +and ysize as parameters. I’ll also make the fillets an option. +brick.py now looks like this:

+
import math
+import ccad
+
+def solidbrick(xsize, ysize, wall_offset, unit, height, draft,
+               knob_rad, knob_height, knob_draft):
+    
+    dx = height * math.tan(math.radians(draft))
+    wbottom = ccad.rectangle(unit * xsize - 2*wall_offset,
+                             unit * ysize - 2*wall_offset)
+    wtop = ccad.rectangle(unit * xsize - 2*dx - 2*wall_offset,
+                          unit * ysize - 2*dx - 2*wall_offset)
+    wtop.translate((dx, dx, height))
+    brick = ccad.loft([wbottom, wtop], True)
+    brick.translate((wall_offset, wall_offset, 0.0))
+
+    drad = knob_height * math.tan(math.radians(knob_draft))
+    knob_top_rad = knob_rad - drad
+    knob_base_rad = knob_rad + drad
+    knob = ccad.cone(knob_base_rad, knob_top_rad, 2 * knob_height)
+    knob.translate((0.5 * unit, 0.5 * unit, height - knob_height))
+
+    for x in range(xsize):
+        for y in range(ysize):
+            brick = brick + ccad.translated(knob, (x * unit, y * unit, 0.0))
+
+    return brick
+
+def brick(xsize, ysize, fillet = True):
+
+    unit = 5.0
+    height = 10.0
+    draft = 1.0 # degrees of draft on faces for plastic ejection
+    knob_rad = 1.8 # radius of the brick knob for mating with other bricks
+    knob_height = 2.0
+    knob_draft = 5.0 # degrees of draft for the knob
+    wall_thickness = 1.0 # plastic wall thickness
+    fillet_rad = 0.4 # the default radius to use for rounded edges
+
+    outerbrick = solidbrick(xsize, ysize, 0.0, unit, height, draft,
+                            knob_rad, knob_height, knob_draft)
+    
+    if fillet:
+        to_fillet = []
+        for count, edge_center in enumerate(outerbrick.subcenters('edge')):
+            if (abs(edge_center[2]) < 0.1 or
+                (abs(edge_center[2] - height) < 0.1 and
+                 abs(edge_center[0] - 0.5*unit) % unit < 0.1 and
+                 abs(edge_center[1] - 0.5*unit) % unit < 0.1)):
+                pass
+            else:
+                to_fillet.append(count)
+        outerbrick.fillet(fillet_rad, to_fillet)
+    
+    innerbrick = solidbrick(xsize, ysize, wall_thickness, unit,
+                            height - wall_thickness, draft,
+                            knob_rad - wall_thickness, knob_height, knob_draft)
+    base = ccad.box(2*unit*xsize, 2*unit*ysize, 1.0)
+    base.translate((-0.5*unit*xsize, -0.5*unit*ysize, -1.0))
+    innerbrick = innerbrick + base
+
+    if fillet:
+        to_fillet = []
+        for count, edge_center in enumerate(innerbrick.subcenters('edge')):
+            if (abs(edge_center[2]) < 0.1 or
+                (abs(edge_center[2] - (height - wall_thickness)) < 0.1 and
+                 abs(edge_center[0] - 0.5*unit) % unit < 0.1 and
+                 abs(edge_center[1] - 0.5*unit) % unit < 0.1)):
+                to_fillet.append(count)
+        innerbrick.fillet(fillet_rad, to_fillet)
+
+    brick = outerbrick - innerbrick
+    
+    post_rad = (math.sqrt(2.0)*unit - 2*knob_rad)/2.0
+    drad = (height - 0.5*wall_thickness) * math.tan(math.radians(draft))
+    post_base_rad = post_rad + drad
+    post = ccad.cone(post_rad, post_base_rad, height - 0.5*wall_thickness)
+    if fillet:
+        post.fillet(fillet_rad, [(0.0, 0.0, 0.0)])
+
+    post.translate((unit, unit, 0.0))
+    for x in range(xsize - 1):
+        for y in range(ysize - 1):
+            brick = brick + ccad.translated(post, (x * unit, y * unit, 0.0))
+
+    return brick
+
+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/example1_drawings.html b/src/contrib/ccad/doc/html/example1_drawings.html new file mode 100644 index 000000000..12cd113cc --- /dev/null +++ b/src/contrib/ccad/doc/html/example1_drawings.html @@ -0,0 +1,196 @@ + + + + + + + Drawings — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Multiple Bricks

+

Next topic

+

Nuances

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Drawings¶

+

OCC has a number of capabilities to turn a model into engineering +drawings with dimensions and annotations. ccad does not yet implement +them, but the hidden line removal features are enough to generate some +simple patent drawings.

+
+

Requirements¶

+

I’m pleased with my brick, and I’d like to create some patent drawings +from it. I’d like top, side, isometric, and bottom views. In case I +change my brick, I’d like the drawings to auto-generate. I’ll use the +4x2 example.

+
+
+

brick_images.py¶

+

I’ll create a file called brick_images.py. It starts like this:

+
import ccad
+import brick
+
+def generate_images(xsize, ysize):
+    pass
+
+if __name__ == '__main__':
+    generate_images()
+
+
+

Currently, it does nothing. I want to write the generate_images +function. First, I’ll create the projection vectors for the various +views:

+
unit = 5.0
+height = 10.0
+center = (unit*xsize/2, unit*ysize/2, height/2)
+projections = {'top': (center, (0.0, 0.0, 1.0), (0.0, 1.0, 0.0)),
+               'side': (center, (1.0, 0.0, 0.0), (0.0, 0.0, 1.0)),
+               'iso': (center,
+                       (math.sqrt(0.4), -math.sqrt(0.4), math.sqrt(0.2)),
+                       (-math.sqrt(0.5), math.sqrt(0.5), 0.0)),
+               'bottom': (center,
+                          (math.sqrt(0.4), -math.sqrt(0.4), -math.sqrt(0.2)),
+                          (-math.sqrt(0.5), math.sqrt(0.5), 0.0))}
+
+
+

Now, I’ll create a brick and view instance:

+
b1 = brick.brick(xsize, ysize, fillet = False)
+v1 = ccad.view()
+
+
+

Note that I called brick with fillet set to False. Smooth edges +(e.g. filleted ones) are not shown in drawing mode. We need them +sharp to see them.

+

Now, I’ll fix the background, scale, and size of the view:

+
v1.set_background((1.0, 1.0, 1.0))
+v1.set_scale(24.0)
+v1.set_size((600, 400))
+
+
+

Finally, I’ll iterate over each projection, display it in black lines, +and save it:

+
for projection in projections:
+    vcenter, vout, vup = projections[projection]
+    v1.clear()
+    v1.set_projection(vcenter, vout, vup)
+    v1.display(b1, color = (0.0, 0.0, 0.0), line_width = 2.0)
+    v1.mode_drawing()
+    v1.save(name = 'brick_' + projection + '.png')
+
+
+

Note the mode_drawing call after display. This redraws the image +in a drafting-like mode.

+

Now, I’ll call generate_images on start:

+
if __name__ == '__main__':
+    generate_images(4, 2)
+
+
+

When I run the program, it generates the following images:

+_images/brick_top.png +_images/brick_side.png +_images/brick_iso.png +_images/brick_bottom.png +
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/example1_multibrick.html b/src/contrib/ccad/doc/html/example1_multibrick.html new file mode 100644 index 000000000..fbcdc52dd --- /dev/null +++ b/src/contrib/ccad/doc/html/example1_multibrick.html @@ -0,0 +1,151 @@ + + + + + + + Multiple Bricks — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Previous topic

+

brick.py

+

Next topic

+

Drawings

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Multiple Bricks¶

+

Because ccad is python-programming, it allows huge power in design +reuse. Our building toy serves as a great example.

+

I’ll create a new file and call it all_bricks.py. First, I’ll +define the various brick sizes I want in the toy:

+
import ccad
+import brick
+
+sizes = [(1, 1),
+         (2, 1),
+         (4, 1),
+         (6, 1),
+         (8, 1),
+         (2, 2),
+         (2, 4),
+         (2, 8),
+         (4, 4)]
+
+
+

Now, I’ll define a display function to view a given brick:

+
def display_brick(widget, view, size):
+    view.clear()
+    view.display(brick.brick(size[0], size[1]))
+
+
+

Finally, I’ll write the code to run at startup:

+
if __name__ == '__main__':
+    v1 = ccad.view()
+    for size in sizes:
+        name = 'brick' + str(size[0]) + 'x' + str(size[1])
+        v1.add_menuitem(('Bricks', name), display_brick, v1, size)
+    ccad.start()
+
+
+

The startup code instantiates a view, adds a menu item for every brick +in sizes and starts the viewer in stand-alone mode.

+

The figure below shows the all_bricks.py window with the 8x1 brick +displayed:

+_images/example1_all_bricks.png +

Try to appreciate the power. With a few more lines, I can generate 9 +different bricks from single brick code, and because it’s python, the +complexity is nearly unlimited. By contrast, complicated model reuse +can be difficult-to-impossible in GUI-based CAD systems.

+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/example1_nuances.html b/src/contrib/ccad/doc/html/example1_nuances.html new file mode 100644 index 000000000..cb0ab0f57 --- /dev/null +++ b/src/contrib/ccad/doc/html/example1_nuances.html @@ -0,0 +1,105 @@ + + + + + + + Nuances — ccad v0.1 documentation + + + + + + + + + + + +
+
+

Previous topic

+

Drawings

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Nuances¶

+

While the building toy example appears to be a step-by-step smooth +process, in reality, it wasn’t. OCC often has trouble with fillets +and boolean operations. I had to play with the model and fillets +before I had something which worked robustly. I originally included +those trails-to-nowhere to help you see how they can be overcome, but +it became over-complicated. I removed them, and it looks simple now.

+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/example1_singlebrick.html b/src/contrib/ccad/doc/html/example1_singlebrick.html new file mode 100644 index 000000000..54bb6b008 --- /dev/null +++ b/src/contrib/ccad/doc/html/example1_singlebrick.html @@ -0,0 +1,319 @@ + + + + + + + A Single Brick — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Previous topic

+

Example - Building Toy

+

Next topic

+

brick.py

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

A Single Brick¶

+

First, I’ll start by importing the required packages:

+
import math
+import ccad
+
+
+

Now, I’ll define some dimensions for all bricks:

+
unit = 5.0
+height = 10.0
+knob_rad = 1.8 # radius of the brick knob for mating with other bricks
+knob_draft = 5.0 # degrees of draft for the knob
+knob_height = 2.0
+wall_thickness = 1.6 # plastic wall thickness
+draft = 1.0 # degrees of draft on faces for plastic ejection
+fillet_rad = 0.4 # the default radius to use for rounded edges
+
+
+

In ipython, I set up the specific brick’s dimensions. Later, these +will be pass parameters in a function:

+
xsize = 4
+ysize = 2
+
+
+

Now, I begin making the outer shape. I could use box, but most +injection molders want you to draft the walls to make machining and +ejection easier. loft* tends to be more robust than **draft on +arbitrary shapes, so I’ll use that:

+
dx = height * math.tan(math.radians(draft))
+wbottom = ccad.rectangle(unit * xsize, unit * ysize)
+wtop = ccad.rectangle(unit * xsize - 2*dx, unit * ysize - 2*dx)
+wtop.translate((dx, dx, height))
+brick = ccad.loft([wbottom, wtop], True)
+
+view1 = ccad.view()
+view1.display(brick)
+
+
+_images/example1_box.png +

Here, the brick bottom is defined by the wire, wbottom, and the +brick top is defined by the wire, wtop. The loft with ruled set +to True converts the wires to a ruled solid:

+

I need a knob on top for mating with bricks above, and I want that +knob to aid in alignment by tapering it. A cone would do nicely:

+
drad = knob_height * math.tan(math.radians(knob_draft))
+knob_top_rad = knob_rad - drad
+knob_base_rad = knob_rad + drad
+knob = ccad.cone(knob_base_rad, knob_top_rad, 2 * knob_height)
+knob.translate((0.5 * unit, 0.5 * unit, height - knob_height))
+
+
+

Notice the knob is higher than it needed to be, then offset. Usually, +boolean operations are less buggy when surfaces don’t coincide on +their faces. That is, make the knob penetrate into the brick for more +robust boolean operations.

+

Now, I add the knob to the view to make sure I like the position. +It’s added in red:

+
view1.display(knob, color = (1.0, 0.0, 0.0))
+
+
+_images/example1_boxwknob.png +

Now, I’ll replicate the knob and fuse it with the brick:

+
for x in range(xsize):
+    for y in range(ysize):
+        brick = brick + ccad.translated(knob, (x * unit, y * unit, 0.0))
+
+view1.clear()
+view1.display(brick)
+
+
+_images/example1_boxwknobs.png +

Note the use of the function form of translate here: +ccad.translated versus the method form for wtop: wtop.translate. +I use the function form when I want to copy a shape. I use the +method form when I want to change a shape.

+

Injection molders like same-thickness walls, so I need to core-out the +brick. At this point, it looks like most of my code can be replicated +for the core, so I’ll place the solid brick in its own function and +add a wall_offset to its pass parameters and place it in a file +called brick.py:

+
import ccad
+import math
+
+def solidbrick(xsize, ysize, wall_offset, unit, height, draft,
+               knob_rad, knob_height, knob_draft):
+
+    dx = height * math.tan(math.radians(draft))
+    wbottom = ccad.rectangle(unit*xsize - 2*wall_offset,
+                             unit*ysize - 2*wall_offset)
+    wtop = ccad.rectangle(unit*xsize - 2*dx - 2*wall_offset,
+                          unit*ysize - 2*dx - 2*wall_offset)
+    wtop.translate((dx, dx, height))
+    brick = ccad.loft([wbottom, wtop], True)
+    brick.translate((wall_offset, wall_offset, 0.0))
+
+    drad = knob_height * math.tan(math.radians(knob_draft))
+    knob_top_rad = knob_rad - drad
+    knob_base_rad = knob_rad + drad
+    knob = ccad.cone(knob_base_rad, knob_top_rad, 2 * knob_height)
+    knob.translate((0.5 * unit, 0.5 * unit, height - knob_height))
+
+    for x in range(xsize):
+        for y in range(ysize):
+            brick = brick + ccad.translated(knob, (x * unit, y * unit, 0.0))
+
+    return brick
+
+
+

And, I can import it in ipython:

+
import brick
+
+outerbrick = brick.solidbrick(xsize, ysize, 0.0, unit, height, draft,
+                              knob_rad, knob_height, knob_draft)
+
+
+

outerbrick is now the outside of the brick.

+

Before I create the inside of the brick, I ought to add fillets to +outerbrick. Many injection molders use milling to form the mold. +Milling requires fillets wherever the ball endmill can’t make a sharp +corner. For the cavity, it’s the convex edges. For the core, it’s +the concave edges. To make things simple, I usually fillet every +edge. However, to show off some of ccad’s features, I’ll fillet only +the edges I must fillet.

+

In outerbrick, I want to fillet the tip of every knob, the side walls, +and the top walls:

+
to_fillet = []
+for count, edge_center in enumerate(outerbrick.subcenters('edge')):
+    if (abs(edge_center[2]) < 0.1 or
+        (abs(edge_center[2] - height) < 0.1 and
+         abs(edge_center[0] - 0.5*unit) % unit < 0.1 and
+         abs(edge_center[1] - 0.5*unit) % unit < 0.1)):
+        pass
+    else:
+        to_fillet.append(count)
+outerbrick.fillet(fillet_rad, to_fillet)
+
+
+

The loop goes through every edge in outerbrick, analyzing the edge’s +center. Edges whose z-component is near zero are not filleted. +Additionally, edges with z-component equal to height and +xy-components near the knob positions are not filleted. All other +edges are filleted. Here’s what I get:

+
v.clear()
+v.display(outerbrick)
+
+
+_images/example1_outerbrick.png +

With the solidbrick function, I can now define the inside of the brick:

+
innerbrick = brick.solidbrick(xsize, ysize, wall_thickness,
+                              unit, height - wall_thickness, draft,
+                              knob_rad - wall_thickness, knob_height,
+                              knob_draft)
+base = ccad.box(2*unit*xsize, 2*unit*ysize, 1.0)
+base.translate((-0.5*unit*xsize, -0.5*unit*ysize, -1.0))
+innerbrick = innerbrick + base
+
+view1.clear()
+view1.display(innerbrick)
+
+
+_images/example1_innerbrick.png +

Note, I have added a base to innerbrick. It will be helpful when I +perform the boolean cut. Finally, I’ll fillet innerbrick:

+
to_fillet = []
+for count, edge_center in enumerate(innerbrick.subcenters('edge')):
+    if (abs(edge_center[2]) < 0.1 or
+        (abs(edge_center[2] - (height - wall_thickness)) < 0.1 and
+         abs(edge_center[0] - 0.5*unit) % unit < 0.1 and
+         abs(edge_center[1] - 0.5*unit) % unit < 0.1)):
+        to_fillet.append(count)
+innerbrick.fillet(fillet_rad, to_fillet)
+
+v.clear()
+v.display(innerbrick)
+
+
+_images/example1_innerbrickfillet.png +

You should recognize the fillet code. It’s the opposite of the edges +filleted for outerbrick, because it’s a core.

+

Finally, I’ll perform the cut:

+
brick = outerbrick - innerbrick
+
+v.clear()
+v.set_projection((0.0, 0.0, 0.0),
+                 (math.sqrt(0.45), -math.sqrt(0.1), -math.sqrt(0.45)),
+                 (0.0, -1.0, 0.0))
+v.display(brick)
+
+
+_images/example1_brick.png +

I’m almost there. I now only need the posts that sit between the +knobs to add more holding force. Like the knobs, I’ll make the posts +cones and fillet the tips:

+
post_rad = (math.sqrt(2.0)*unit - 2*knob_rad)/2.0
+drad = (height - 0.5*wall_thickness) * math.tan(math.radians(draft))
+post_base_rad = post_rad + drad
+post = ccad.cone(post_rad, post_base_rad, height - 0.5*wall_thickness)
+post.fillet(fillet_rad, [(0.0, 0.0, 0.0)])
+
+
+_images/example1_post.png +

Here, I’ve chosen to pass a list of the center positions of the edges +to be filleted to the fillet method. In this case, I’m only filleting +one edge, the tip.

+

Now, I translate it and add it to the brick:

+
post.translate((unit/2, unit/2, 0.0))
+for x in range(xsize - 1):
+    for y in range(ysize - 1):
+        brick = brick + ccad.translated(post, (x * unit, y * unit, 0.0))
+
+
+_images/example1_brickpost.png +
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/file_transfer.html b/src/contrib/ccad/doc/html/file_transfer.html new file mode 100644 index 000000000..3f32aa62f --- /dev/null +++ b/src/contrib/ccad/doc/html/file_transfer.html @@ -0,0 +1,202 @@ + + + + + + + File Transfer — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Gathering Information and Miscellaneous Methods

+

Next topic

+

Displaying

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

File Transfer¶

+

Sometimes, you’ll want to save and restore data. Also, you’ll want to +transfer designs with the outside world. ccad offers the following +features.

+
+

to_brep / from_brep¶

+

All shapes offers a to_brep method to store a shape in OCC’s native +format:

+
s1 = cm.sphere(1.0)
+s1.to_brep('sphere.brep')
+
+
+

ccad also offers the function from_brep to import a .brep file:

+
s1 = ccad.from_brep('sphere.brep')
+
+
+
+
+

to_iges / from_iges¶

+

All shapes offer a to_iges method to store shapes in IGES format:

+
s1 = cm.sphere(1.0)
+s1.to_iges('sphere.igs')
+
+
+

ccad also offers the function from_iges to import an IGES file:

+
s1 = ccad.from_iges('sphere.igs')
+
+
+

There is one slight nuance to this example: the shape type changed:

+
s1.stype
+'face'
+
+
+

To fix, use the brep_mode pass parameter:

+
s1.to_iges('sphere.igs', brep_mode = 1)
+s1 = ccad.from_iges('sphere.igs')
+s1.stype
+'solid'
+
+
+
+
+

to_step / from_step¶

+

All shapes offer a to_step method to store shapes in STEP format:

+
s1 = cm.sphere(1.0)
+s1.to_step('sphere.stp')
+
+
+

ccad also offers the function from_step to import a STEP file:

+
s1 = ccad.from_step('sphere.stp')
+
+
+
+
+

to_stl¶

+

Solids offer a to_stl method to store solids in STL format:

+
s1 = cm.sphere(1.0)
+s1.to_stl('sphere.stl')
+
+
+

ccad does not offer a from_stl function.

+
+
+

from_svg¶

+

ccad offers a from_svg function to import a two dimensional SVG file. +It converts each svg path into a wire and returns a list of wires:

+
ws = ccad.from_svg('logo.svg')
+for w in ws:
+    view1.display(w)
+
+
+

The .svg file

+_images/logo.png +

ccad’s conversion of it

+_images/from_svg.png +
+
+

Notes¶

+

Many of these methods and functions offer considerably more conversion +options than shown. Consult the API for all options.

+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/genindex.html b/src/contrib/ccad/doc/html/genindex.html new file mode 100644 index 000000000..ca116b9c8 --- /dev/null +++ b/src/contrib/ccad/doc/html/genindex.html @@ -0,0 +1,89 @@ + + + + + + + Index — ccad v0.1 documentation + + + + + + + + + +
+
+ + + + + +
+
+ +
+
+
+
+ + +

Index

+ + + +
+ + + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/interactive.html b/src/contrib/ccad/doc/html/interactive.html new file mode 100644 index 000000000..d190fffe6 --- /dev/null +++ b/src/contrib/ccad/doc/html/interactive.html @@ -0,0 +1,157 @@ + + + + + + + Interactive Viewing — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Stand-Alone Viewing

+

Next topic

+

Using Display

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Interactive Viewing¶

+
+

Introduction¶

+

To run the viewer in stand-alone mode, you need ipython. Start +ipython with:

+
ipython -gthread
+
+
+

The ccad viewer uses gtk; that’s why you use the -gthread option.

+

Now, from the ipython prompt, you can work with ccad interactively. +Try this:

+
In [1]: import ccad
+In [2]: s1 = ccad.sphere(1.0)
+In [3]: s2 = ccad.box(1.0, 2.0, 3.0)
+In [4]: s2.translate((2.0, 0.0, 0.0))
+In [5]: v1 = ccad.view()
+
+

After the last line, you should see a window appear with nothing in +it. v1 is an instance of a viewing window. Now type:

+
In [6]: v1.display(s1)
+
+

You should see s1 in your window. Move to the window, hold on the +middle mouse button and pan. You should see the shape moving. Now, +go back to the ipython prompt and type:

+
In [7]: v1.display(s2)
+
+

You’ll see s2 appear in the viewer.

+

To clear the window, use:

+
In [8]: v1.clear()
+
+

You have interactive viewing.

+

Now, start a second window with:

+
In [9]: v2 = ccad.view()
+
+

v2 is an instance of the second displayed window. Add something +different to it:

+
In [10]: v2.display(ccad.cone(4.0, 2.0, 2.0))
+
+

Note that each viewer is independent. You may create as many view +windows as you like.

+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/intro.html b/src/contrib/ccad/doc/html/intro.html new file mode 100644 index 000000000..2a07c3a50 --- /dev/null +++ b/src/contrib/ccad/doc/html/intro.html @@ -0,0 +1,140 @@ + + + + + + + Introduction — ccad v0.1 documentation + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Contents

+

Next topic

+

Details

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Introduction¶

+

ccad is a text-based mechanical CAD (computer aided design) tool. It +is a python module you import within a python file or from the python +prompt. Once imported, you can create and view mechanical objects.

+

A simple example may help. From the ipython prompt, I can type the +following:

+
In [1]: import ccad
+In [2]: s1 = ccad.sphere(2.0)
+In [3]: v1 = ccad.view()
+In [4]: v1.display(s1)
+
+

After the third command, a window appears, and after the last command, +it looks like this:

+_images/sphere_example.png +

I’ve created and displayed a sphere of radius 2.0. Now, suppose I +want to 3D print this sphere. From the prompt, I can type:

+
In [5]: s1.to_stl('sphere.stl')
+
+

And, I have my sphere in .stl format, ready to ship to a 3D printer. +ccad lets you do much more than creating a sphere. To learn more, +read on.

+
+

This Document¶

+

This document and the examples shown provide an overview of what’s +available in ccad. Consult the API (easily available with pydoc) for +more options and better descriptions of the passed parameters.

+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/logging.html b/src/contrib/ccad/doc/html/logging.html new file mode 100644 index 000000000..e4b755e32 --- /dev/null +++ b/src/contrib/ccad/doc/html/logging.html @@ -0,0 +1,539 @@ + + + + + + + Gathering Information and Miscellaneous Methods — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Boolean Operations

+

Next topic

+

File Transfer

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Gathering Information and Miscellaneous Methods¶

+
+

Overview¶

+

Shapes can report a variety of information about themselves. These +information gathering queries all operate as class methods. +Additionally, there are some methods that didn’t fit the modelling +categories before. They’re below.

+

All classes support the following methods:

+
+

copy¶

+

All shapes offer a copy method. copy returns a copy of the shape. +The following example makes s2 a new copy of s1:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s2 = s1.copy()
+
+
+
+
+

subshapes¶

+

subshapes reports the shapes of a specific type that make up a shape. +The following example sets edges to a list of the edges in s1:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+edges = s1.subshapes('edge')
+
+
+

The passed parameter may be vertex, edge, wire, face, or shell.

+
+
+

bounds¶

+

bounds places an imaginary box around the shape and returns a 6-tuple +that describes the minimum and maximum (x, y, z) boundaries of the +box:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s1.bounds()
+(-9.9999999999999995e-08, -9.9999999999999995e-08, -9.9999999999999995e-08,
+1.0000001000000001, 2.0000000999999998, 3.0000000999999998)
+
+
+
+
+

center¶

+

center returns the center of the shape:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s1.center()
+(0.5, 1.0, 1.5)
+
+
+
+
+

subcenters¶

+

subcenters returns a list of the centers of the subshapes of a shape:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s1.subcenters('edge')
+[(0.0, 0.0, 1.5), (0.0, 1.0, 3.0), (0.0, 2.0, 1.5), (0.0, 1.0, 0.0),
+(1.0, 0.0, 1.5), (1.0, 1.0, 3.0), (1.0, 2.0, 1.5), (1.0, 1.0, 0.0),
+(0.5, 0.0, 0.0), (0.5, 0.0, 3.0), (0.5, 2.0, 0.0), (0.5, 2.0, 3.0)]
+
+
+
+
+

check¶

+

check performs an OCC BRep check on the shape to make sure there’s +nothing wrong with it. It returns 1 if it’s okay:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s1.check()
+1
+
+
+
+
+

fix¶

+

fix attempts to fix a faulty shape. It sometimes works and sometimes +doesn’t. It’s usually best to find out what caused the shape to be +corrupted in the first place and fix that. It does nothing if the +shape’s okay:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s1.fix()
+
+
+
+
+

nearest¶

+

nearest returns the index of the subshape whose center is closest to a +passed position. It operates on a list of positions:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s1.nearest('edge', [(0.0, 1.0, 3.0), (1.0, 1.0, 0.0)])
+[1, 7]
+
+
+

Note how these indices coincide with the correct indices in the +subcenters call above.

+
+
+

subtolerance¶

+

subtolerance returns the min, average, and max tolerance on every +subshape. When called with ‘all’, it iterates through all subshape +types:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s1.subtolerance('edge')
+(1.0001000025000001e-07, 1.0001000025000002e-07, 1.0001000025000001e-07)
+
+
+

The vertex, edge, and face class also support a tolerance method, +which returns the tolerance of that shape itself.

+
+
+

dump¶

+

dump returns the index, center, and tolerance of every subshape:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s1.dump()
+shell0 location: (0.500000,1.000000,1.500000)
+face0 location: (0.000000,1.000000,1.500000) tolerance: 1.0000e-07
+face1 location: (1.000000,1.000000,1.500000) tolerance: 1.0000e-07
+face2 location: (0.500000,0.000000,1.500000) tolerance: 1.0000e-07
+face3 location: (0.500000,2.000000,1.500000) tolerance: 1.0000e-07
+face4 location: (0.500000,1.000000,0.000000) tolerance: 1.0000e-07
+face5 location: (0.500000,1.000000,3.000000) tolerance: 1.0000e-07
+wire0 location: (0.000000,1.000000,1.500000)
+wire1 location: (1.000000,1.000000,1.500000)
+wire2 location: (0.500000,0.000000,1.500000)
+wire3 location: (0.500000,2.000000,1.500000)
+wire4 location: (0.500000,1.000000,0.000000)
+wire5 location: (0.500000,1.000000,3.000000)
+edge0 location: (0.000000,0.000000,1.500000) tolerance: 1.0000e-07
+edge1 location: (0.000000,1.000000,3.000000) tolerance: 1.0000e-07
+edge2 location: (0.000000,2.000000,1.500000) tolerance: 1.0000e-07
+edge3 location: (0.000000,1.000000,0.000000) tolerance: 1.0000e-07
+edge4 location: (1.000000,0.000000,1.500000) tolerance: 1.0000e-07
+edge5 location: (1.000000,1.000000,3.000000) tolerance: 1.0000e-07
+edge6 location: (1.000000,2.000000,1.500000) tolerance: 1.0000e-07
+edge7 location: (1.000000,1.000000,0.000000) tolerance: 1.0000e-07
+edge8 location: (0.500000,0.000000,0.000000) tolerance: 1.0000e-07
+edge9 location: (0.500000,0.000000,3.000000) tolerance: 1.0000e-07
+edge10 location: (0.500000,2.000000,0.000000) tolerance: 1.0000e-07
+edge11 location: (0.500000,2.000000,3.000000) tolerance: 1.0000e-07
+vertex0 location: (0.000000,0.000000,3.000000) tolerance: 1.0000e-07
+vertex1 location: (0.000000,0.000000,0.000000) tolerance: 1.0000e-07
+vertex2 location: (0.000000,2.000000,3.000000) tolerance: 1.0000e-07
+vertex3 location: (0.000000,2.000000,0.000000) tolerance: 1.0000e-07
+vertex4 location: (1.000000,0.000000,3.000000) tolerance: 1.0000e-07
+vertex5 location: (1.000000,0.000000,0.000000) tolerance: 1.0000e-07
+vertex6 location: (1.000000,2.000000,3.000000) tolerance: 1.0000e-07
+vertex7 location: (1.000000,2.000000,0.000000) tolerance: 1.0000e-07
+
+

Output may also be printed as a hierarchy.

+
+
+
+

vertex¶

+

The vertex class supports no additional methods.

+
+
+

edge¶

+
+

type¶

+

type returns the type of edge:

+
e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2)
+e1.type()
+'ellipse'
+
+
+
+
+

length¶

+

length returns the length of the edge:

+
e1 = ccad.circle(1.0)
+e1.length()
+6.2831853071795862
+
+
+
+
+

poly¶

+

poly returns a polyline approximation to an edge:

+
e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2)
+e1.poly()
+[(-3.6738190614671318e-16, 1.0, 0.0),
+(0.15691819145569041, 0.99691733373312796, 0.0),
+(0.31286893008046135, 0.98768834059513777, 0.0),
+(0.46689072771180956, 0.97236992039767678, 0.0),
+(0.61803398874989446, 0.95105651629515364, 0.0),
+(0.76536686473018001, 0.92387953251128663, 0.0),
+(0.90798099947909328, 0.8910065241883679, 0.0),
+(1.0449971294318967, 0.85264016435409251, 0.0),
+(1.1755705045849458, 0.80901699437494756, 0.0),
+(1.2988960966603678, 0.76040596560003082, 0.0),
+(1.4142135623730947, 0.70710678118654768, 0.0),
+(1.520811931200061, 0.64944804833018421, 0.0),
+(1.5706338617614892, 0.6190939493098343, 0.0),
+(1.6180339887498947, 0.58778525229247336, 0.0),
+(1.6691465074426051, 0.55089698145210275, 0.0),
+(1.716897587203732, 0.51289927740590635, 0.0),
+(1.7696268722994766, 0.46594547235582523, 0.0),
+(1.8172246744657483, 0.41764053997213163, 0.0),
+(1.8460369350307941, 0.38475564794493639, 0.0),
+(1.8724697412794742, 0.35137482408134291, 0.0),
+(1.8964890225744522, 0.3175410946848452, 0.0),
+(1.9180638191979309, 0.28329806983275019, 0.0),
+(1.9371663222572619, 0.24868988716485535, 0.0),
+(1.9537719095292947, 0.21376115499211573, 0.0),
+(1.9678591771972589, 0.17855689479863776, 0.0),
+(1.9794099674392696, 0.14312248321112042, 0.0),
+(1.9884093918329029, 0.10750359351052567, 0.0),
+(1.9948458505456679, 0.071746136761379878, 0.0),
+(1.9987110472866427, 0.035896202634582597, 0.0),
+(2.0, 2.4492127076447545e-16, 0.0)]
+
+
+
+
+
+

wire¶

+

The wire class can be called with a list of edges to be combined into +a wire.

+
+

length¶

+

Like the length method in edge, edge returns the length of the wire.

+
+
+

poly¶

+

Like the poly method in edge, poly returns a polyline +approximation to a wire.

+
+
+
+

face¶

+
+

fillet¶

+

fillet allows you to fillet a face at passed vertices. The following +example fillets the upper right corner and lower left corner of a square:

+
w1 = ccad.polygon([(-1.0, -1.0, 0.0),
+                   (1.0, -1.0, 0.0),
+                   (1.0, 1.0, 0.0),
+                   (-1.0, 1.0, 0.0),
+                   (-1.0, -1.0, 0.0)])
+f1 = ccad.plane(w1)
+f1.fillet(0.25, [(1.0, 1.0, 0.0), (-1.0, -1.0, 0.0)]))
+
+_images/logging_face_fillet.png +
+
+

wire¶

+

wire returns the outer wire of a face.

+
+
+

inner_wires¶

+

inner_wires returns the inner wires of a face.

+
+
+

type¶

+

type returns the type of mathematical surface a face sits on:

+
s1 = ccad.cone(4.0, 2.0, 2.0)
+faces = s1.subshapes('face')
+map(lambda x: x.type(), faces)
+['cone', 'plane', 'plane']
+
+
+
+
+

area¶

+

area returns the area of the face:

+
f1 = ccad.plane(cm.wire([ccad.circle(1.0)]))
+f1.area()
+3.141592653589794
+
+
+
+
+
+

shell¶

+

The shell class can be called with a list of faces to be sewed into a +shell.

+
+

area¶

+

Like the area method in face, area returns the area of the shell.

+
+
+
+

solid¶

+

The solid class can be called with a list of shells to be combined +into a solid.

+
+

fillet¶

+

fillet allows you to fillet edges. The following example fillets all +the edges on the positive x-side of the cube:

+
s1 = ccad.box(1.0, 1.0, 1.0)
+s1.fillet(0.25, [(1.0, 0.5, 0.0),
+                 (1.0, 0.5, 1.0),
+                 (1.0, 0.0, 0.5),
+                 (1.0, 1.0, 0.5)])
+
+
+_images/logging_solid_fillet.png +

Filleting can be buggy. I found the following things helped improve +success rate:

+
+
    +
  • Eliminate impossible conditions (e.g. fillet radius is 0.6 on a +1x1x1 box).
  • +
  • Eliminate unneeded edges. OCC’s boolean operations often return +two faces in the same domain with an edge between them that can be +merged. Eliminating these edges by merging the faces helped. The +simplify method can do this for some shapes.
  • +
  • Move problem edges out of the way. Cylinder and sphere edges are +necessary in OCC, but their position can often be rotated away +from a problem fillet location.
  • +
  • Slice the solid (with a box or something) into sections. Then +fuse those sections back together. Then fillet.
  • +
  • Change the fillet radius slightly.
  • +
  • Fillet a few edges, then a few more, then a few more, etc. For +example, if you have a shape with multiple pockets, fillet one +pocket, then the next, then the next. The fillet operation tended +to fail when the number of fillets got large.
  • +
+
+
+
+

chamfer¶

+

chamfer allows you to chamfer edges. The following example chamfers +three edges in a cube:

+
s1 = ccad.box(1.0, 1.0, 1.0)
+s1.chamfer(0.25, [(1.0, 0.5, 0.0),
+                  (1.0, 0.5, 1.0),
+                  (1.0, 0.0, 0.5),
+                  (1.0, 1.0, 0.5)])
+
+
+_images/logging_solid_chamfer.png +
+
+

draft¶

+

draft places a draft on the faces specified. The following example +drafts the vertical edges of a cube:

+
s1 = ccad.box(1.0, 1.0, 1.0)
+s1.translate((-0.5, -0.5, 0.0))
+face_centers = s1.subcenters('face')
+to_draft = []
+for count, face_center in enumerate(face_centers):
+    if abs(face_center[2] - 0.5) < 0.1:
+        to_draft.append(count)
+s1.draft(math.radians(5.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0), to_draft)
+
+
+_images/logging_solid_draft.png +
+
+

volume¶

+

volume returns the volume in cubic units of the solid:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s1.volume()
+6.0
+
+
+
+
+

simplify¶

+

Boolean operations can often leave more faces than are necessary, +particularly when faces are coincident. OCC hasn’t fixed this issue, +so I fixed it, although my implementation won’t fix all cases:

+
s1 = ccad.box(1.0, 1.0, 1.0)
+s2 = s1.copy()
+s2.translate((1.0, 0.5, 0.5))
+s3 = s1 - s2
+len(s3.subshapes('face'))
+7
+
+
+_images/logging_solid_simplify1.png +

Note s3 has an extra face. (I’ve only drawn edges, but you get the +point). Now, let’s simplify it:

+
s3.simplify()
+len(s3.subshapes('face'))
+6
+
+
+_images/logging_solid_simplify2.png +

Note the extra face is removed.

+
+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/modelling.html b/src/contrib/ccad/doc/html/modelling.html new file mode 100644 index 000000000..9cf75aba8 --- /dev/null +++ b/src/contrib/ccad/doc/html/modelling.html @@ -0,0 +1,236 @@ + + + + + + + Modelling — ccad v0.1 documentation + + + + + + + + + + + +
+
+

Previous topic

+

Details

+

Next topic

+

Creating Edges

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Modelling¶

+

Modelling describes creating models in ccad. To understand modelling +well, it’s helpful to understand how a model is described in ccad. +ccad models are defined by a sequence of simpler shapes and +boundaries. In CAD terminology, it’s called a Boundary Representation +model, or BRep, for short. Consider a cube.

+_images/cube_solid.png +

The entire cube is called a solid. A solid is made of one or more +outer layers called shells. For example, if the cube had a hollow +center, it would be made of two shells. One shell would define the +outside of the cube. The other would define the inside. Each shell +is made of one or more faces.

+_images/cube_face.png +

Each face has a single mathematical expression that defines the whole +face. Planes and spheres are examples of faces. Each face is bounded +by one or more wires.

+_images/cube_wire.png +

The wires define the boundary of the face. Each wire is made of one or +more edges.

+_images/cube_edge.png +

Edges, like faces, have a unique mathematical expression that defines +the entire edge. Each edge is bounded by two vertices.

+_images/cube_vertex.png +

Put in a hierarchy, it looks like this

+
+
+
solid
+
+
shell
+
+
face
+
+
wire
+
+
edge
+
vertex
+
+
+
+
+
+
+
+
+
+
+

ccad calls all of these shapes. Each shape has its own class in +ccad. During modelling, you may need to use all of these classes. I +use wire much. I use the others rarely. Usually, I’m calling a +higher level function, like sphere, that instantiates a solid, or arc, +that instantiates an edge.

+ +
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/objects.inv b/src/contrib/ccad/doc/html/objects.inv new file mode 100644 index 000000000..6fb8f4ea6 --- /dev/null +++ b/src/contrib/ccad/doc/html/objects.inv @@ -0,0 +1,3 @@ +# Sphinx inventory version 1 +# Project: ccad +# Version: 0.1 diff --git a/src/contrib/ccad/doc/html/search.html b/src/contrib/ccad/doc/html/search.html new file mode 100644 index 000000000..c5044ecfb --- /dev/null +++ b/src/contrib/ccad/doc/html/search.html @@ -0,0 +1,91 @@ + + + + + + + Search — ccad v0.1 documentation + + + + + + + + + + +
+
+
+
+ +
+
+
+
+ +

Search

+
+ +

+ Please activate JavaScript to enable the search + functionality. +

+
+

+ From here you can search these documents. Enter your search + words into the box below and click "search". Note that the search + function will automatically search for all of the words. Pages + containing fewer words won't appear in the result list. +

+
+ + + +
+ +
+ +
+ +
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/searchindex.js b/src/contrib/ccad/doc/html/searchindex.js new file mode 100644 index 000000000..7ce77d5d2 --- /dev/null +++ b/src/contrib/ccad/doc/html/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({desctypes:{},terms:{represent:15,all:[0,20,2,15,5,12,18,14],code:[0,2,5,7,12,18,13],edg:[10,20,23,2,3,4,22,11,7,15,8,9],queri:20,lack:12,prefix:12,ellips:[10,20,15],higher:[2,15],abil:12,follow:[19,0,20,23,3,5,22,11,17,12,18,10,16,21,6,14],mill:2,consider:14,whose:[2,20],brepprimapi_spher:12,depend:11,system:[13,12,9],fancier:19,intermedi:[10,19],pulldown:19,program:[3,13,21,5],becam:1,under:19,larg:20,wavi:17,everi:[2,13,20],string:19,fals:3,mous:[4,24],graphic:5,mechan:[6,12],veri:[17,4],all_brick:13,pt2:10,pt1:10,tri:12,subtoler:20,level:[19,15],did:12,gui:[13,5],list:[20,2,5,22,11,14],iter:[3,20],"try":[13,4,24],item:[19,11,13],vector:[0,3],math:[10,0,20,2,3,22,11,7,12],polylin:20,round:[2,11,7],pleas:[3,18],upper:20,endmil:2,direct:[16,0,11,21],annot:3,zero:2,cost:12,design:[14,13,6,12,5],viewtyp:21,pass:[19,0,20,2,3,4,22,6,7,10,11,14],"0001000025000002e":20,further:12,"4x2":3,append:[2,0,7,20],blue:0,index:[20,4],what:[2,6,20,11],appear:[1,6,24,5],neg:11,section:[23,20],fillet:[1,20,23,2,3,4,16,7],current:[3,11],version:[19,0,11,12],intersect:11,"new":[0,11,20,13],blender:4,method:[19,0,20,2,15,21,23,14,9],contrast:13,wall_offset:[2,7],elimin:[23,20],themselv:20,deriv:[23,11,15,9],nuanc:[1,14,9,18],gener:[13,3,11,12,18],here:2,satisfi:7,to_fillet:[2,7],let:[6,20,12],ver:12,along:11,vertic:[20,15],modifi:18,valu:11,box:[0,20,23,2,15,5,11,7,16,21,24],great:13,convers:14,taper:[2,16],shift:4,larger:11,xvzf:12,chang:[0,20,2,3,4,11,12,14],hexagon:[22,11],glu:23,diamet:11,love:18,extra:20,appli:23,modul:6,scalex:0,layer:15,scalez:0,revol:[11,15],"boolean":[1,20,2,15,23,9],uniqu:15,instal:[12,9],scalei:0,unit:[20,2,3,21,7,18],from:[10,20,23,3,21,4,5,22,11,17,12,16,13,6,24],describ:[0,20,15],would:[2,15,21],"4492127076447545e":20,visit:17,two:[23,20,14,15],next:[20,5],few:[22,19,13,20],call:[19,0,20,23,2,3,21,15,5,22,16,7,12,10,17,13],usr:12,black:3,vcenter:3,type:[20,4,21,6,14,24],until:5,minor:10,more:[20,23,2,15,22,6,17,12,16,13,14],add_menu:[19,8],exit:5,vout:3,peopl:12,notic:2,trail:1,face_from:[17,15],mirrorz:0,hole:11,hold:[2,4,24],must:[2,23,17],middl:[4,24],endpoint:10,restor:[14,21],setup:12,work:[1,20,16,17,12,24],focu:12,bezier_con:16,minimum:20,can:[0,1,20,23,2,21,4,5,10,6,12,11,13,8,24],learn:[22,6],root:12,def:[19,2,3,5,7,13],cad:[13,6,12,15],control:[8,9,21],nearest:20,prompt:[6,24,18],tar:12,process:1,lock:5,mirrorx:0,indic:[23,20],high:[22,16],sphere:[20,23,15,5,16,12,21,6,14,24],sourc:12,want:[19,0,2,3,5,6,17,18,13,8,14],sharp:[2,3],tan:[2,7],mirrori:0,alwai:12,surfac:[2,16,17,20],multipl:[20,22,11,18,13,9],turn:[22,3],add_menuitem:[19,13,8],cylind:[23,16,11,20,15],faulti:20,degre:[2,0,7],write:[0,13,3],how:[1,20,12,15,21],main:5,iso:3,simpl:[2,3,1,6,19],updat:12,map:20,recogn:2,penetr:2,haven:12,huge:13,max:20,after:[3,21,23,5,6,24],triedron:21,befor:[2,1,20],mac:12,plane:[20,11,17,15],okai:20,mai:[19,6,20,15,24],arch:11,averag:20,"short":15,attempt:20,practic:17,third:6,sit:[2,20],caus:20,inform:[20,15,9],combin:20,shade:[4,21],anoth:[10,19,17],volum:20,first:[19,20,2,3,22,13],origin:[10,11,0,1],six:16,help:[1,20,23,2,15,21,6],arc_ellips:[10,20,15],over:[3,1],move:[0,20,15,23,4,9,24],becaus:[2,13,12],inner_wir:20,patent:3,top:[2,3,16,4],through:[10,2,20],same:[0,20,2,4,10,23,18],wire2:20,subshap:20,hierarchi:[19,20,15],still:12,paramet:[19,0,20,2,6,7,14],outer:[2,20,15],fit:[20,21,10,11,17,8],knob_height:[2,7],chosen:2,fix:[3,20,14],gtk:[19,12,24],better:[16,6],window:[5,21,6,12,13,24],button:[4,24],hidden:[3,4,21],therefor:12,might:19,pixel:21,them:[23,11,3,1,20],"return":[0,20,23,2,10,16,7,11,14],thei:[11,1,20],python:[13,6,12,5],auto:3,handi:4,rectangl:[22,2,7,15],number:[3,20,12,21],mode_wirefram:[8,21],terminolog:15,front:[4,21],now:[1,20,2,3,5,6,7,13,8,24],from_ig:[14,15],introduct:[6,8,12,9,24],chamfer:[23,20],display_car:19,didn:20,easili:6,simple_glu:23,radian:[10,2,7,20],each:[10,0,23,3,15,5,22,16,17,14,24],found:[23,20],redraw:3,side:[20,2,3,4,22,16,8],from_stl:14,rotatedi:0,domain:[23,20],weight:10,car2:19,car1:19,procedur:12,rotatedx:0,rotatedz:0,connect:[22,15],our:13,todo:[],orient:[0,11],out:[2,23,20,4],shown:[16,3,6,17,14],closest:20,space:17,goe:2,newli:23,your:[19,21,12,5,8,24],categori:20,mate:[2,7],print:[6,20],got:20,correct:20,red:[2,0,21],foreground:21,linear:11,insid:[2,11,17,15],manipul:19,differ:[13,17,12,24],standard:[4,21],reason:12,base:[2,6,7,13],usual:[2,20,15,5],put:[12,15],span:21,octopu:11,line_width:3,"10mm":18,capabl:3,angl:22,could:[2,21],wrong:20,pythonocc:12,keep:[12,4],thing:[2,20],length:20,to_ig:[14,15],place:[2,0,7,20,18],isn:[23,17,12],outsid:[2,11,14,15],imposs:[13,20],lambda:20,oper:[1,20,2,15,23,9],dimens:[2,0,3],major:10,directli:22,onc:[6,4,5],independ:24,scene:[4,21],wedg:[16,15],knob_base_rad:[2,7],alreadi:21,wrapper:12,wasn:1,thick:[2,7],open:12,straight:[10,11],size:[0,13,3,21],given:[0,11,13],dump:20,start:[2,3,13,24,5],data:14,interact:[8,9,24],sometim:[23,16,20,14],least:12,plastic:[2,7,18],toi:[1,13,9,18],draft:[2,3,7,20,21],too:[0,12],circl:[10,20,15,22,16,11],white:21,isometr:3,"final":[2,3,13,5],store:14,inner:20,wtop:[2,7],shell:[11,20,15],option:[19,21,11,7,17,16,6,14,24],tool:6,copi:[2,20],"1x1x1":20,brep_mod:14,specifi:20,part:[23,11],consult:[16,6,14],exactli:[23,17],than:[19,20,2,16,6,14],png:[3,21],serv:13,wide:22,provid:[11,6],remov:[3,1,20,4,21],rate:20,project:[3,12,21],reus:13,stp:14,str:13,subcent:[2,7,20],posit:[23,2,0,11,20],seri:10,comput:6,viewer:[5,19,21,13,8,24],vertex5:20,ani:[4,21],packag:2,to_draft:20,have:[20,23,2,15,21,6,12,8,24],"__main__":[3,13,5],need:[23,2,3,15,11,17,12,24],knob_draft:[2,7],featur:[2,3,14],issu:20,engin:[3,12],squar:[20,11,17],min:20,bezier:[10,15],click:4,note:[20,23,2,3,15,16,11,14,24],also:[0,20,10,23,7,14],robustli:1,exampl:[19,0,1,20,10,23,3,15,21,22,6,17,18,11,16,13,14,9],build:[1,13,9,18],which:[1,20,23,5,10,11,12],noth:[3,20,24],singl:[0,2,15,11,18,13,9],even:4,begin:2,printer:6,allow:[13,20,12,4],set_foreground:[8,21],object:[23,0,6],reset_mode_draw:21,most:2,plai:1,regular:21,xsize:[2,3,7],segment:[22,10,11,15],why:[11,12,24],don:2,slightli:20,eject:[2,7],clear:[2,3,21,13,8,24],later:[22,2],metal:21,face:[20,23,2,4,11,7,15,17,16,8,14,9],pipe:[11,15],mode_shad:[8,21],unchang:0,boundari:[20,15],brep:[20,14,15],show:[2,13,18],api:[19,23,17,16,6,14],opposit:[2,4],radiu:[10,20,2,22,16,7,6],syntax:12,particularli:[23,20],corner:[22,2,20],find:[23,20],view1:[2,14],onli:[2,11,20,4],locat:[23,20],menu:[19,13,4],occ:[1,20,23,3,11,12,14],configur:[19,8,9,18],figur:13,helic:[22,11],should:[2,24,12,5],suppos:[6,18],analyz:2,factor:0,local:12,sure:[2,20],spun:11,"8x1":13,cube:[20,15],nearli:13,variou:[3,13,4],get:[2,11,20,12],handli:12,express:15,pocket:[23,20],nativ:14,drawn:[0,20],report:20,requir:[2,3,12,9,18],solidbrick:[2,7],ngon:[22,11,17,15],gather:[20,15,9],pydoc:[6,12],stl:[6,14],common:[23,15],nowher:1,where:12,view:[2,3,21,4,5,6,13,8,9,24],sew:20,set:[2,3,20,21],keypad:4,startup:13,hasn:20,maximum:20,display_brick:13,see:[19,1,3,4,5,23,8,24],arc:[22,10,11,15],fail:20,close:[22,11,17,21],mode_draw:[3,8,21],face3:20,best:20,extrud:[16,11],statu:4,wire:[20,2,15,22,11,17,14,9],kei:4,someth:[23,1,20,4,24],scaledi:0,tend:[2,20],face4:20,face5:20,enough:3,won:[20,4,18],face0:20,face1:20,face2:20,extrus:11,"import":[2,3,5,6,7,12,13,14,24],awai:[23,20],scaledx:0,scaledz:0,appreci:13,extend:[10,16,12],hotkei:4,set_background:[3,8,21],screen:21,extens:21,entir:[15,21],len:20,ipython:[2,6,7,24,18],toler:20,come:[0,12],addit:20,edge3:20,both:17,last:[22,19,6,24,5],wirefram:[4,21],howev:[2,23,12,21],alon:[13,8,24,9,5],equal:[22,2],etc:20,instanc:[3,12,24],vup:3,mani:[2,16,14,24],corrupt:20,opencascad:12,whole:15,simpli:12,point:[10,0,20,2,21,22],color:[2,3,21],had:[11,1,15],height:[2,3,7,18],post_base_rad:[2,7],path:14,coincid:[2,23,20],poli:20,toru:[16,15],brick_:3,second:24,addition:[2,20,12],fillet_rad:[2,7],compon:2,"5mm":18,much:[6,15],interest:4,brick:[2,3,7,18,13,9],from_step:[14,15],loft:[2,11,7,15],imag:3,convert:[2,14],argument:[19,12],coordin:[4,21],understand:[15,5],togeth:[23,20],rang:[2,7],those:[23,1,20,4],"case":[2,3,20,4],replic:2,subsequ:23,look:[19,1,2,15,21,6,7,12,18],solid:[20,23,2,15,11,4,16,14,9],align:2,easier:2,defin:[10,19,2,15,22,13],"while":[1,4],ought:2,abov:[2,0,20],aid:[2,6],loop:2,wire0:20,wire1:20,set_triedron:[8,21],wire3:20,wall_thick:[2,7],wire5:20,almost:2,edge10:20,edge11:20,edge0:20,edge1:20,edge2:20,itself:20,edge4:20,cone:[20,2,15,16,7,24],edge6:20,edge7:20,edge8:20,edge9:20,unneed:[23,20],quit:[8,21],generate_imag:3,develop:[12,18],helix:[22,11,15],face_cent:20,perform:[2,0,23,20],make:[20,2,5,22,11,7],format:[6,14],fewer:0,sqrt:[10,2,3,7],complex:13,elong:22,success:20,document:[6,17,12,9],pan:[8,4,24],difficult:13,ball:2,screenshot:21,cubic:20,mode_hlr:[8,21],octagon:17,hand:0,fairli:12,fuse:[2,23,20,15],user:[12,9,5],improv:20,robust:[2,23],wherev:2,implement:[3,20],submenu:19,lower:[22,19,20],off:2,center:[10,20,2,3,15,22,16,4],vertex1:20,vertex0:20,vertex3:20,vertex2:20,well:[12,15],vertex4:20,vertex7:20,vertex6:20,without:10,command:6,thi:[19,0,20,23,2,3,4,5,15,6,7,12,11,14,9,24],titl:19,model:[19,1,20,3,15,5,13,8,9],from_svg:[14,15],dimension:14,left:[22,20,4],rotatei:0,distanc:[22,11,4],just:12,less:[2,12],when:[19,0,20,2,3,4,23,17,18],halfwai:21,rotatex:[0,11],shape:[0,20,23,2,4,5,10,11,15,12,17,21,8,14,9,24],shorthand:0,skill:12,glue:[23,15],yet:[3,12],versu:2,cut:[2,23,15],miscellan:[20,15,9],screendump:21,gtkglext:12,littl:21,pythoncc:12,instanti:[10,15,5,22,16,17,13],add:[2,13,24,12,19],overview:[6,20,15],els:[2,7],save:[0,21,8,3,14],approxim:20,match:17,knob:[2,7],applic:19,around:[20,4],transpar:21,read:[23,6,5],wall:[2,7],swig:12,rotatez:0,know:[19,23,12,4,18],background:[3,21],press:4,world:14,hollow:15,outerbrick:[2,7],name:[3,13],like:[19,0,20,2,3,15,5,6,7,12,21,24],specif:[2,20,4],arbitrari:[2,11,4,21],spine:11,manual:[22,12],zoom:[8,4],post_rad:[2,7],"6738190614671318e":20,necessari:[23,20,12],either:23,output:20,brepprimapi:12,nice:2,page:12,right:[0,20,4],old:[0,23],often:[22,20,1,17,23],simplifi:[23,20],linux:12,some:[23,2,3,11,20],back:[23,20,24],intern:[12,9],enumer:[2,7,20],set_siz:[3,8,21],mirror:[0,15,9],successfulli:12,slice:[23,11,20,15],bottom:[2,3,16],set_scal:[3,8,21],overlap:23,mathemat:[20,17,15],leav:20,inject:[2,18],overcom:1,caviti:2,condit:20,content:9,complic:[1,13],refer:4,machin:2,core:2,run:[3,5,12,13,8,24],power:[13,12],slight:14,widget:[19,13],imaginari:20,ident:16,perpendicular:16,to_brep:[14,15],cumbersom:12,step:[1,14],although:20,offset:[2,11,15],"__name__":[3,13,5],post:[2,7],gthread:24,between:[2,23,20],simpler:[12,15],"9999999999999995e":20,about:[0,20,5,22,23,11],quarter:10,rare:[23,15],materi:21,ysize:[2,3,7],knob_rad:[2,7],helical_solid:[11,15],unfortun:[23,11,12],revolut:16,stand:[13,8,24,9,5],block:18,own:[2,15,5],pythonpath:12,within:[6,21],bound:[20,17,15],brick_imag:[3,18],three:[0,20,4],down:[19,4],wire4:20,notat:0,wrap:12,spline:[10,17,15],merg:[23,11,20],triangl:11,log:23,wai:[23,20],area:20,transfer:[15,14,9],support:20,rotat:[0,23,20,15,9],"long":[11,21],custom:[19,21],avail:[23,19,6],width:21,interfac:5,includ:1,select:[19,8,4],"function":[19,0,23,2,3,4,5,22,16,7,15,10,17,13,14],from_brep:[14,15],hard:12,form:[2,0,23,4],offer:[16,20,14,4],forc:2,tupl:[19,20],translat:[0,20,23,2,5,22,11,7,24],molder:2,line:[5,0,3,4,21,10,23,13,24],buggi:[2,20],"true":[2,7],count:[2,7,20],pull:19,made:15,shell0:20,"default":[2,7,21],wbottom:[2,7],smooth:[3,1],displai:[19,23,2,3,21,4,5,6,13,8,14,9,24],troubl:[23,11,1,12],directori:12,below:[23,13,20],problem:[23,20],similar:[16,18],stype:[11,14],curv:10,mirroredz:0,mirroredx:0,creat:[19,23,2,3,15,5,22,6,17,10,11,16,13,9,24],flat:17,certain:21,dure:15,convex:2,doesn:[19,11,20],mode:[3,4,21,13,8,24],mirroredi:0,exist:[19,0,23],file:[2,3,15,21,10,6,12,18,13,14,9],logo:14,ship:6,doe:[20,3,17,14],mold:[2,18],check:20,fill:[17,15,4],"0000e":20,readi:6,polygon:[22,11,20,15],set_project:[2,3,8,21],extract:12,edge_cent:[2,7],tip:2,detail:[19,16,12,9],planar:17,prism:[11,15],other:[23,2,11,7,15],futur:18,varieti:20,sprite:21,knob_top_rad:[2,7],you:[19,0,1,20,23,2,4,5,22,6,15,12,17,8,14,24],unlimit:13,realiti:1,viewstandard:[8,21],"0001000025000001e":20,draw:[3,21,9,18],sequenc:15,"class":[22,0,20,15],vertex:[20,15,22,11,4,8],drad:[2,7],scale:[0,15,3,9,21],consid:[11,15],to_step:[14,15],concav:2,ccad:[10,0,20,21,2,3,17,4,5,22,15,6,7,12,23,11,16,13,8,14,24],axi:0,innerbrick:[2,7],algorithm:23,edge5:20,svg:[10,14],descript:6,orbit:[8,4],rule:[2,0],to_stl:[6,14,15],portion:[10,23],text:[6,8],profil:11},titles:["Moving, Rotating, Mirroring, and Scaling","Nuances","A Single Brick","Drawings","Using Display","Stand-Alone Viewing","Introduction","brick.py","Displaying","Contents","Creating Edges","Creating Derived Shapes","Details","Multiple Bricks","File Transfer","Modelling","Creating Solids","Creating Faces","Example - Building Toy","Configuring Display","Gathering Information and Miscellaneous Methods","Controlling Display","Creating Wires","Boolean Operations","Interactive Viewing"],modules:{},descrefs:{},filenames:["trms","example1_nuances","example1_singlebrick","example1_drawings","using_display","stand_alone","intro","example1_brickpy","displaying","contents","creating_edges","creating_derived","details","example1_multibrick","file_transfer","modelling","creating_solids","creating_faces","example1","configuring_display","logging","controlling_display","creating_wires","boolean","interactive"]}) \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/stand_alone.html b/src/contrib/ccad/doc/html/stand_alone.html new file mode 100644 index 000000000..601b35f28 --- /dev/null +++ b/src/contrib/ccad/doc/html/stand_alone.html @@ -0,0 +1,152 @@ + + + + + + + Stand-Alone Viewing — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Previous topic

+

Displaying

+

Next topic

+

Interactive Viewing

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Stand-Alone Viewing¶

+

To run the viewer stand-alone, from your own python program, call +ccad.display() on all the shapes you’d like to see, then call +ccad.start(), which will start the GUI (Graphical User Interface). +The following python program creates a sphere and a box, calls the +display function on each, then starts the viewer:

+
import ccad
+
+def main():
+    s1 = ccad.sphere(1.0)
+    s2 = ccad.box(1.0, 2.0, 3.0)
+    s2.translate((2.0, 0.0, 0.0))
+    v1 = ccad.view()
+    v1.display(s1)
+    v1.display(s2)
+    ccad.start()
+
+if __name__ == '__main__':
+    main()
+
+
+

If you’ve read about modelling, you should understand the listing up +to the view line:

+
v1 = ccad.view()
+
+
+

which instantiates a ccad viewing window. The next line:

+
v1.display(s1)
+
+
+

displays s1 in the v1 window. And:

+
v1.display(s2)
+
+
+

displays s2 in the v1 window. The final line:

+
ccad.start()
+
+
+

starts the viewer. Once you start the viewer, the program is locked +until you exit the viewer. Usually, you want to make this the last +line in your code.

+

After running, the following window appears

+_images/display_spherebox1.png +

Now, you can view your design.

+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/trms.html b/src/contrib/ccad/doc/html/trms.html new file mode 100644 index 000000000..dded4ce7b --- /dev/null +++ b/src/contrib/ccad/doc/html/trms.html @@ -0,0 +1,194 @@ + + + + + + + Moving, Rotating, Mirroring, and Scaling — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Creating Derived Shapes

+

Next topic

+

Boolean Operations

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Moving, Rotating, Mirroring, and Scaling¶

+

translate, rotate, mirror, and scale on shapes can be called with a +class method. For example:

+
s1.translate((1.0, 0.0, 0.0))
+
+
+

This translates s1 in-place. ccad comes with a function call form +too with “ed” appended. For example:

+
s2 = ccad.translated(s1, (1.0, 0.0, 0.0))
+
+
+

The function form returns a new shape. s2 is s1 translated. s1 +is unchanged. It can be useful when you want to save the old shape or +write code with fewer lines.

+

In each of the following examples, s1 describes the old object +drawn in red, and s2 describes the new object drawn in blue.

+
+

Moving¶

+

translate changes a shape’s position but not its orientation. The +following example moves the box 2.0 in the x-direction, -6.0 in the +y-direction, and 4.0 in the z-direction:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s2 = ccad.translated(s1, (2.0, -6.0, 4.0))
+
+
+_images/trms_translate.png +
+
+

Rotation¶

+

rotate rotates a shape about a point and a direction vector. Rotation +follows the right hand rule. The following example rotates the box 90 +degrees about the z-axis:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s2 = ccad.rotated(s1, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), math.pi/2)
+
+
+_images/trms_rotate.png +

There are also three shorthand notations when rotation is about the +origin and a single axis: rotatex, rotatey, and rotatez +(or rotatedx, rotatedy, and rotatedz in function form). +The following example performs the same rotation above but in +shorthand:

+
s2 = ccad.rotatedz(math.pi/2)
+
+
+
+
+

Mirroring¶

+

mirror mirrors the shape about a point in a given direction. The +following example mirrors the box about the origin in the x-direction:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s2 = ccad.mirrored(s1, (0.0, 0.0, 0.0), (1.0, 0.0, 0.0))
+
+
+_images/trms_mirror.png +

Like rotate, shorthand versions exist of mirrorx, mirrory, and +mirrorz (or mirroredx, mirroredy, and mirroredz in +function form).

+
+
+

Scaling¶

+

scale changes the size of the shape by scale factors. The following +example scales the box in the x-direction and then moves it:

+
s1 = ccad.box(1.0, 2.0, 3.0)
+s2 = ccad.scaled(s1, 2.0, 1.0, 1.0)
+s2.translate((4.0, 0.0, 0.0))
+
+
+_images/trms_scale.png +

Passing a single parameter to scale scales all three dimensions the +same.

+

Like rotate and mirror, shorthand versions exist of scalex, +scaley, and scalez (or scaledx, scaledy, and +scaledz in function form).

+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/html/using_display.html b/src/contrib/ccad/doc/html/using_display.html new file mode 100644 index 000000000..9eb048d0c --- /dev/null +++ b/src/contrib/ccad/doc/html/using_display.html @@ -0,0 +1,216 @@ + + + + + + + Using Display — ccad v0.1 documentation + + + + + + + + + + + + +
+
+

Table Of Contents

+ + +

Previous topic

+

Interactive Viewing

+

Next topic

+

Controlling Display

+

This Page

+ + + +
+
+ +
+
+
+
+ +
+

Using Display¶

+
+

View -> Mode¶

+

ccad offers three forms of viewing: wireframe, shaded (filled solids), +and hidden line removal.

+_images/display_wireframe.png +_images/display_shaded.png +_images/display_hlr.png +
+
+

View -> Side¶

+

Various standard views can be selected from the View -> Side menu, or +you can use Blender hotkeys:

+
+
    +
  • keypad 7 for top view
  • +
  • keypad 1 for front view
  • +
  • keypad 3 for right view
  • +
  • hold shift with those keys to see the opposite view
  • +
+
+
+
+

View -> Orbit¶

+

Orbiting allows you to orbit around the scene, keeping the scene the +same distance from you. You can use the menus, you can press the +middle mouse button and move the mouse, or you can use Blender hotkeys:

+
+
    +
  • keypad 8 to orbit up
  • +
  • keypad 2 to orbit down
  • +
  • keypad 4 to orbit left
  • +
  • keypad 6 to orbit right
  • +
+
+
+
+

View -> Pan¶

+

Pan allows you to move the center of the scene. You can use the +menus, you can hold shift while pressing the middle mouse button and +moving the mouse, or you can use Blender hotkeys:

+
+
    +
  • shift keypad 8 to pan up
  • +
  • shift keypad 2 to pan down
  • +
  • shift keypad 4 to pan left
  • +
  • shift keypad 6 to pan right
  • +
+
+
+
+

View -> Zoom¶

+

You can zoom in or out by using the menus, or using the Blender hotkeys:

+
+
    +
  • keypad + to zoom in
  • +
  • keypad - to zoom out
  • +
+
+
+
+

Select -> Shape¶

+

In Select -> Shape mode, you select shapes. Move to the shape you are +interested in, and right click on it. The shape is selected. In this +case, shape refers to something you passed to the display function, +not any arbitrary shape. If you only passed one shape to the display +function, there is only one shape to select.

+
+
+

Select -> Face¶

+

In Select -> Face mode, you can select a face on a shape you selected. +Right click on the face of interest, and you’ll see the face index, +type, and center on the status line.

+_images/select_face.png +
+
+

Select -> Edge¶

+

In Select -> Edge mode, you can select an edge on a shape you +selected. Right click on the edge of interest, and you’ll see the +edge index and center on the status line.

+_images/select_edge.png +

Edge selection can be very handy when trying to fillet specific edges. +Once you know the edge’s coordinates (or even index if the shape won’t +change), you can pass those coordinates to the fillet function.

+
+
+

Select -> Vertex¶

+

In Select -> Vertex mode, you can select a vertex on a shape you +selected. Right click on the vertex of interest, and you’ll see the +vertex index and center on the status line.

+_images/select_vertex.png +
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/contrib/ccad/doc/interactive.rst b/src/contrib/ccad/doc/interactive.rst new file mode 100644 index 000000000..a387741c2 --- /dev/null +++ b/src/contrib/ccad/doc/interactive.rst @@ -0,0 +1,52 @@ +Interactive Viewing +=================== + +Introduction +------------ + +To run the viewer in stand-alone mode, you need ipython. Start +ipython with:: + + ipython -gthread + +The ccad viewer uses gtk; that's why you use the -gthread option. + +Now, from the ipython prompt, you can work with ccad interactively. +Try this:: + + In [1]: import ccad + In [2]: s1 = ccad.sphere(1.0) + In [3]: s2 = ccad.box(1.0, 2.0, 3.0) + In [4]: s2.translate((2.0, 0.0, 0.0)) + In [5]: v1 = ccad.view() + +After the last line, you should see a window appear with nothing in +it. **v1** is an instance of a viewing window. Now type:: + + In [6]: v1.display(s1) + +You should see **s1** in your window. Move to the window, hold on the +middle mouse button and pan. You should see the shape moving. Now, +go back to the ipython prompt and type:: + + In [7]: v1.display(s2) + +You'll see **s2** appear in the viewer. + +To clear the window, use:: + + In [8]: v1.clear() + +You have interactive viewing. + +Now, start a second window with:: + + In [9]: v2 = ccad.view() + +**v2** is an instance of the second displayed window. Add something +different to it:: + + In [10]: v2.display(ccad.cone(4.0, 2.0, 2.0)) + +Note that each viewer is independent. You may create as many view +windows as you like. diff --git a/src/contrib/ccad/doc/intro.rst b/src/contrib/ccad/doc/intro.rst new file mode 100644 index 000000000..bc890135a --- /dev/null +++ b/src/contrib/ccad/doc/intro.rst @@ -0,0 +1,35 @@ +Introduction +============ + +ccad is a text-based mechanical CAD (computer aided design) tool. It +is a python module you import within a python file or from the python +prompt. Once imported, you can create and view mechanical objects. + +A simple example may help. From the ipython prompt, I can type the +following:: + + In [1]: import ccad + In [2]: s1 = ccad.sphere(2.0) + In [3]: v1 = ccad.view() + In [4]: v1.display(s1) + +After the third command, a window appears, and after the last command, +it looks like this: + +.. image:: sphere_example.png + +I've created and displayed a sphere of radius 2.0. Now, suppose I +want to 3D print this sphere. From the prompt, I can type:: + + In [5]: s1.to_stl('sphere.stl') + +And, I have my sphere in .stl format, ready to ship to a 3D printer. +ccad lets you do much more than creating a sphere. To learn more, +read on. + +This Document +------------- + +This document and the examples shown provide an overview of what's +available in ccad. Consult the API (easily available with pydoc) for +more options and better descriptions of the passed parameters. diff --git a/src/contrib/ccad/doc/logging.rst b/src/contrib/ccad/doc/logging.rst new file mode 100644 index 000000000..a9bbbdd7e --- /dev/null +++ b/src/contrib/ccad/doc/logging.rst @@ -0,0 +1,408 @@ +Gathering Information and Miscellaneous Methods +=============================================== + +Overview +-------- + +Shapes can report a variety of information about themselves. These +information gathering queries all operate as class methods. +Additionally, there are some methods that didn't fit the modelling +categories before. They're below. + +All classes support the following methods: + +copy +^^^^ + +All shapes offer a copy method. copy returns a copy of the shape. +The following example makes **s2** a new copy of **s1**:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = s1.copy() + +subshapes +^^^^^^^^^ + +subshapes reports the shapes of a specific type that make up a shape. +The following example sets **edges** to a list of the edges in **s1**:: + + s1 = ccad.box(1.0, 2.0, 3.0) + edges = s1.subshapes('edge') + +The passed parameter may be *vertex*, *edge*, *wire*, *face*, or *shell*. + +bounds +^^^^^^ + +bounds places an imaginary box around the shape and returns a 6-tuple +that describes the minimum and maximum (x, y, z) boundaries of the +box:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.bounds() + (-9.9999999999999995e-08, -9.9999999999999995e-08, -9.9999999999999995e-08, + 1.0000001000000001, 2.0000000999999998, 3.0000000999999998) + +center +^^^^^^ + +center returns the center of the shape:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.center() + (0.5, 1.0, 1.5) + +subcenters +^^^^^^^^^^ + +subcenters returns a list of the centers of the subshapes of a shape:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.subcenters('edge') + [(0.0, 0.0, 1.5), (0.0, 1.0, 3.0), (0.0, 2.0, 1.5), (0.0, 1.0, 0.0), + (1.0, 0.0, 1.5), (1.0, 1.0, 3.0), (1.0, 2.0, 1.5), (1.0, 1.0, 0.0), + (0.5, 0.0, 0.0), (0.5, 0.0, 3.0), (0.5, 2.0, 0.0), (0.5, 2.0, 3.0)] + +check +^^^^^ + +check performs an OCC BRep check on the shape to make sure there's +nothing wrong with it. It returns 1 if it's okay:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.check() + 1 + +fix +^^^ + +fix attempts to fix a faulty shape. It sometimes works and sometimes +doesn't. It's usually best to find out what caused the shape to be +corrupted in the first place and fix that. It does nothing if the +shape's okay:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.fix() + +nearest +^^^^^^^ + +nearest returns the index of the subshape whose center is closest to a +passed position. It operates on a list of positions:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.nearest('edge', [(0.0, 1.0, 3.0), (1.0, 1.0, 0.0)]) + [1, 7] + +Note how these indices coincide with the correct indices in the +subcenters call above. + +subtolerance +^^^^^^^^^^^^ + +subtolerance returns the min, average, and max tolerance on every +subshape. When called with 'all', it iterates through all subshape +types:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.subtolerance('edge') + (1.0001000025000001e-07, 1.0001000025000002e-07, 1.0001000025000001e-07) + +The vertex, edge, and face class also support a *tolerance* method, +which returns the tolerance of that shape itself. + +dump +^^^^ + +dump returns the index, center, and tolerance of every subshape:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.dump() + shell0 location: (0.500000,1.000000,1.500000) + face0 location: (0.000000,1.000000,1.500000) tolerance: 1.0000e-07 + face1 location: (1.000000,1.000000,1.500000) tolerance: 1.0000e-07 + face2 location: (0.500000,0.000000,1.500000) tolerance: 1.0000e-07 + face3 location: (0.500000,2.000000,1.500000) tolerance: 1.0000e-07 + face4 location: (0.500000,1.000000,0.000000) tolerance: 1.0000e-07 + face5 location: (0.500000,1.000000,3.000000) tolerance: 1.0000e-07 + wire0 location: (0.000000,1.000000,1.500000) + wire1 location: (1.000000,1.000000,1.500000) + wire2 location: (0.500000,0.000000,1.500000) + wire3 location: (0.500000,2.000000,1.500000) + wire4 location: (0.500000,1.000000,0.000000) + wire5 location: (0.500000,1.000000,3.000000) + edge0 location: (0.000000,0.000000,1.500000) tolerance: 1.0000e-07 + edge1 location: (0.000000,1.000000,3.000000) tolerance: 1.0000e-07 + edge2 location: (0.000000,2.000000,1.500000) tolerance: 1.0000e-07 + edge3 location: (0.000000,1.000000,0.000000) tolerance: 1.0000e-07 + edge4 location: (1.000000,0.000000,1.500000) tolerance: 1.0000e-07 + edge5 location: (1.000000,1.000000,3.000000) tolerance: 1.0000e-07 + edge6 location: (1.000000,2.000000,1.500000) tolerance: 1.0000e-07 + edge7 location: (1.000000,1.000000,0.000000) tolerance: 1.0000e-07 + edge8 location: (0.500000,0.000000,0.000000) tolerance: 1.0000e-07 + edge9 location: (0.500000,0.000000,3.000000) tolerance: 1.0000e-07 + edge10 location: (0.500000,2.000000,0.000000) tolerance: 1.0000e-07 + edge11 location: (0.500000,2.000000,3.000000) tolerance: 1.0000e-07 + vertex0 location: (0.000000,0.000000,3.000000) tolerance: 1.0000e-07 + vertex1 location: (0.000000,0.000000,0.000000) tolerance: 1.0000e-07 + vertex2 location: (0.000000,2.000000,3.000000) tolerance: 1.0000e-07 + vertex3 location: (0.000000,2.000000,0.000000) tolerance: 1.0000e-07 + vertex4 location: (1.000000,0.000000,3.000000) tolerance: 1.0000e-07 + vertex5 location: (1.000000,0.000000,0.000000) tolerance: 1.0000e-07 + vertex6 location: (1.000000,2.000000,3.000000) tolerance: 1.0000e-07 + vertex7 location: (1.000000,2.000000,0.000000) tolerance: 1.0000e-07 + +Output may also be printed as a hierarchy. + +vertex +------ + +The vertex class supports no additional methods. + +edge +---- + +type +^^^^ + +type returns the type of edge:: + + e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2) + e1.type() + 'ellipse' + +length +^^^^^^ + +length returns the length of the edge:: + + e1 = ccad.circle(1.0) + e1.length() + 6.2831853071795862 + +poly +^^^^ + +poly returns a polyline approximation to an edge:: + + e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2) + e1.poly() + [(-3.6738190614671318e-16, 1.0, 0.0), + (0.15691819145569041, 0.99691733373312796, 0.0), + (0.31286893008046135, 0.98768834059513777, 0.0), + (0.46689072771180956, 0.97236992039767678, 0.0), + (0.61803398874989446, 0.95105651629515364, 0.0), + (0.76536686473018001, 0.92387953251128663, 0.0), + (0.90798099947909328, 0.8910065241883679, 0.0), + (1.0449971294318967, 0.85264016435409251, 0.0), + (1.1755705045849458, 0.80901699437494756, 0.0), + (1.2988960966603678, 0.76040596560003082, 0.0), + (1.4142135623730947, 0.70710678118654768, 0.0), + (1.520811931200061, 0.64944804833018421, 0.0), + (1.5706338617614892, 0.6190939493098343, 0.0), + (1.6180339887498947, 0.58778525229247336, 0.0), + (1.6691465074426051, 0.55089698145210275, 0.0), + (1.716897587203732, 0.51289927740590635, 0.0), + (1.7696268722994766, 0.46594547235582523, 0.0), + (1.8172246744657483, 0.41764053997213163, 0.0), + (1.8460369350307941, 0.38475564794493639, 0.0), + (1.8724697412794742, 0.35137482408134291, 0.0), + (1.8964890225744522, 0.3175410946848452, 0.0), + (1.9180638191979309, 0.28329806983275019, 0.0), + (1.9371663222572619, 0.24868988716485535, 0.0), + (1.9537719095292947, 0.21376115499211573, 0.0), + (1.9678591771972589, 0.17855689479863776, 0.0), + (1.9794099674392696, 0.14312248321112042, 0.0), + (1.9884093918329029, 0.10750359351052567, 0.0), + (1.9948458505456679, 0.071746136761379878, 0.0), + (1.9987110472866427, 0.035896202634582597, 0.0), + (2.0, 2.4492127076447545e-16, 0.0)] + +wire +---- + +The wire class can be called with a list of edges to be combined into +a wire. + +length +^^^^^^ + +Like the length method in edge, edge returns the length of the wire. + +poly +^^^^ + +Like the poly method in edge, poly returns a polyline +approximation to a wire. + +face +---- + +fillet +^^^^^^ + +fillet allows you to fillet a face at passed vertices. The following +example fillets the upper right corner and lower left corner of a square:: + + w1 = ccad.polygon([(-1.0, -1.0, 0.0), + (1.0, -1.0, 0.0), + (1.0, 1.0, 0.0), + (-1.0, 1.0, 0.0), + (-1.0, -1.0, 0.0)]) + f1 = ccad.plane(w1) + f1.fillet(0.25, [(1.0, 1.0, 0.0), (-1.0, -1.0, 0.0)])) + +.. image:: logging_face_fillet.png + +wire +^^^^ + +wire returns the outer wire of a face. + +inner_wires +^^^^^^^^^^^ + +inner_wires returns the inner wires of a face. + +type +^^^^ + +type returns the type of mathematical surface a face sits on:: + + s1 = ccad.cone(4.0, 2.0, 2.0) + faces = s1.subshapes('face') + map(lambda x: x.type(), faces) + ['cone', 'plane', 'plane'] + +area +^^^^ + +area returns the area of the face:: + + f1 = ccad.plane(cm.wire([ccad.circle(1.0)])) + f1.area() + 3.141592653589794 + +shell +----- + +The shell class can be called with a list of faces to be sewed into a +shell. + +area +^^^^ + +Like the area method in face, area returns the area of the shell. + +solid +----- + +The solid class can be called with a list of shells to be combined +into a solid. + +fillet +^^^^^^ + +fillet allows you to fillet edges. The following example fillets all +the edges on the positive x-side of the cube:: + + s1 = ccad.box(1.0, 1.0, 1.0) + s1.fillet(0.25, [(1.0, 0.5, 0.0), + (1.0, 0.5, 1.0), + (1.0, 0.0, 0.5), + (1.0, 1.0, 0.5)]) + +.. image:: logging_solid_fillet.png + +Filleting can be buggy. I found the following things helped improve +success rate: + + - Eliminate impossible conditions (e.g. fillet radius is 0.6 on a + 1x1x1 box). + + - Eliminate unneeded edges. OCC's boolean operations often return + two faces in the same domain with an edge between them that can be + merged. Eliminating these edges by merging the faces helped. The + *simplify* method can do this for some shapes. + + - Move problem edges out of the way. Cylinder and sphere edges are + necessary in OCC, but their position can often be rotated away + from a problem fillet location. + + - Slice the solid (with a box or something) into sections. Then + fuse those sections back together. Then fillet. + + - Change the fillet radius slightly. + + - Fillet a few edges, then a few more, then a few more, etc. For + example, if you have a shape with multiple pockets, fillet one + pocket, then the next, then the next. The fillet operation tended + to fail when the number of fillets got large. + +chamfer +^^^^^^^ + +chamfer allows you to chamfer edges. The following example chamfers +three edges in a cube:: + + s1 = ccad.box(1.0, 1.0, 1.0) + s1.chamfer(0.25, [(1.0, 0.5, 0.0), + (1.0, 0.5, 1.0), + (1.0, 0.0, 0.5), + (1.0, 1.0, 0.5)]) + +.. image:: logging_solid_chamfer.png + +draft +^^^^^ + +draft places a draft on the faces specified. The following example +drafts the vertical edges of a cube:: + + s1 = ccad.box(1.0, 1.0, 1.0) + s1.translate((-0.5, -0.5, 0.0)) + face_centers = s1.subcenters('face') + to_draft = [] + for count, face_center in enumerate(face_centers): + if abs(face_center[2] - 0.5) < 0.1: + to_draft.append(count) + s1.draft(math.radians(5.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0), to_draft) + +.. image:: logging_solid_draft.png + +volume +^^^^^^ + +volume returns the volume in cubic units of the solid:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s1.volume() + 6.0 + +simplify +^^^^^^^^ + +Boolean operations can often leave more faces than are necessary, +particularly when faces are coincident. OCC hasn't fixed this issue, +so I fixed it, although my implementation won't fix all cases:: + + s1 = ccad.box(1.0, 1.0, 1.0) + s2 = s1.copy() + s2.translate((1.0, 0.5, 0.5)) + s3 = s1 - s2 + len(s3.subshapes('face')) + 7 + +.. image:: logging_solid_simplify1.png + +Note **s3** has an extra face. (I've only drawn edges, but you get the +point). Now, let's simplify it:: + + s3.simplify() + len(s3.subshapes('face')) + 6 + +.. image:: logging_solid_simplify2.png + +Note the extra face is removed. + diff --git a/src/contrib/ccad/doc/logging_face_fillet.png b/src/contrib/ccad/doc/logging_face_fillet.png new file mode 100644 index 000000000..c21053cad Binary files /dev/null and b/src/contrib/ccad/doc/logging_face_fillet.png differ diff --git a/src/contrib/ccad/doc/logging_solid_chamfer.png b/src/contrib/ccad/doc/logging_solid_chamfer.png new file mode 100644 index 000000000..77ca72a82 Binary files /dev/null and b/src/contrib/ccad/doc/logging_solid_chamfer.png differ diff --git a/src/contrib/ccad/doc/logging_solid_draft.png b/src/contrib/ccad/doc/logging_solid_draft.png new file mode 100644 index 000000000..5ecb5fd04 Binary files /dev/null and b/src/contrib/ccad/doc/logging_solid_draft.png differ diff --git a/src/contrib/ccad/doc/logging_solid_fillet.png b/src/contrib/ccad/doc/logging_solid_fillet.png new file mode 100644 index 000000000..154c5933c Binary files /dev/null and b/src/contrib/ccad/doc/logging_solid_fillet.png differ diff --git a/src/contrib/ccad/doc/logging_solid_simplify1.png b/src/contrib/ccad/doc/logging_solid_simplify1.png new file mode 100644 index 000000000..4b7745f27 Binary files /dev/null and b/src/contrib/ccad/doc/logging_solid_simplify1.png differ diff --git a/src/contrib/ccad/doc/logging_solid_simplify2.png b/src/contrib/ccad/doc/logging_solid_simplify2.png new file mode 100644 index 000000000..3786ad086 Binary files /dev/null and b/src/contrib/ccad/doc/logging_solid_simplify2.png differ diff --git a/src/contrib/ccad/doc/logo.png b/src/contrib/ccad/doc/logo.png new file mode 100644 index 000000000..28c5cfe3d Binary files /dev/null and b/src/contrib/ccad/doc/logo.png differ diff --git a/src/contrib/ccad/doc/logo.svg b/src/contrib/ccad/doc/logo.svg new file mode 100644 index 000000000..c813ac76b --- /dev/null +++ b/src/contrib/ccad/doc/logo.svg @@ -0,0 +1,83 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/src/contrib/ccad/doc/modelling.rst b/src/contrib/ccad/doc/modelling.rst new file mode 100644 index 000000000..1e9337d21 --- /dev/null +++ b/src/contrib/ccad/doc/modelling.rst @@ -0,0 +1,63 @@ +Modelling +========= + +Modelling describes creating models in ccad. To understand modelling +well, it's helpful to understand how a model is described in ccad. +ccad models are defined by a sequence of simpler shapes and +boundaries. In CAD terminology, it's called a Boundary Representation +model, or BRep, for short. Consider a cube. + +.. image:: cube_solid.png + +The entire cube is called a **solid**. A solid is made of one or more +outer layers called **shells**. For example, if the cube had a hollow +center, it would be made of two shells. One shell would define the +outside of the cube. The other would define the inside. Each shell +is made of one or more **faces**. + +.. image:: cube_face.png + +Each face has a single mathematical expression that defines the whole +face. Planes and spheres are examples of faces. Each face is bounded +by one or more **wires**. + +.. image:: cube_wire.png + +The wires define the boundary of the face. Each wire is made of one or +more **edges**. + +.. image:: cube_edge.png + +Edges, like faces, have a unique mathematical expression that defines +the entire edge. Each edge is bounded by two **vertices**. + +.. image:: cube_vertex.png + +Put in a hierarchy, it looks like this + + solid + shell + face + wire + edge + vertex + +ccad calls all of these **shapes**. Each shape has its own class in +ccad. During modelling, you may need to use all of these classes. I +use ``wire`` much. I use the others rarely. Usually, I'm calling a +higher level function, like sphere, that instantiates a solid, or arc, +that instantiates an edge. + +.. toctree:: + :maxdepth: 2 + + creating_edges + creating_wires + creating_faces + creating_solids + creating_derived + trms + boolean + logging + file_transfer + diff --git a/src/contrib/ccad/doc/select_edge.png b/src/contrib/ccad/doc/select_edge.png new file mode 100644 index 000000000..fed7b7767 Binary files /dev/null and b/src/contrib/ccad/doc/select_edge.png differ diff --git a/src/contrib/ccad/doc/select_face.png b/src/contrib/ccad/doc/select_face.png new file mode 100644 index 000000000..0c8d174b0 Binary files /dev/null and b/src/contrib/ccad/doc/select_face.png differ diff --git a/src/contrib/ccad/doc/select_vertex.png b/src/contrib/ccad/doc/select_vertex.png new file mode 100644 index 000000000..cbdc22965 Binary files /dev/null and b/src/contrib/ccad/doc/select_vertex.png differ diff --git a/src/contrib/ccad/doc/solid_box.png b/src/contrib/ccad/doc/solid_box.png new file mode 100644 index 000000000..fe081e641 Binary files /dev/null and b/src/contrib/ccad/doc/solid_box.png differ diff --git a/src/contrib/ccad/doc/solid_cone.png b/src/contrib/ccad/doc/solid_cone.png new file mode 100644 index 000000000..7916be497 Binary files /dev/null and b/src/contrib/ccad/doc/solid_cone.png differ diff --git a/src/contrib/ccad/doc/solid_cylinder.png b/src/contrib/ccad/doc/solid_cylinder.png new file mode 100644 index 000000000..ad87267ef Binary files /dev/null and b/src/contrib/ccad/doc/solid_cylinder.png differ diff --git a/src/contrib/ccad/doc/solid_sphere.png b/src/contrib/ccad/doc/solid_sphere.png new file mode 100644 index 000000000..ba06f7e45 Binary files /dev/null and b/src/contrib/ccad/doc/solid_sphere.png differ diff --git a/src/contrib/ccad/doc/solid_torus.png b/src/contrib/ccad/doc/solid_torus.png new file mode 100644 index 000000000..e919f0f06 Binary files /dev/null and b/src/contrib/ccad/doc/solid_torus.png differ diff --git a/src/contrib/ccad/doc/solid_wedge.png b/src/contrib/ccad/doc/solid_wedge.png new file mode 100644 index 000000000..3eccca6be Binary files /dev/null and b/src/contrib/ccad/doc/solid_wedge.png differ diff --git a/src/contrib/ccad/doc/sphere_example.png b/src/contrib/ccad/doc/sphere_example.png new file mode 100644 index 000000000..21c4c6e83 Binary files /dev/null and b/src/contrib/ccad/doc/sphere_example.png differ diff --git a/src/contrib/ccad/doc/stand_alone.rst b/src/contrib/ccad/doc/stand_alone.rst new file mode 100644 index 000000000..0c19a8acf --- /dev/null +++ b/src/contrib/ccad/doc/stand_alone.rst @@ -0,0 +1,37 @@ +Stand-Alone Viewing +=================== + +To run the viewer stand-alone, from your own python program, call +ccad.display() on all the shapes you'd like to see, then call +ccad.start(), which will start the GUI (Graphical User Interface). +The following python program creates a sphere and a box, calls the +display function on each, then starts the viewer: + +.. literalinclude:: display_spherebox1.py + +If you've read about modelling, you should understand the listing up +to the view line:: + + v1 = ccad.view() + +which instantiates a ccad viewing window. The next line:: + + v1.display(s1) + +displays s1 in the v1 window. And:: + + v1.display(s2) + +displays s2 in the v1 window. The final line:: + + ccad.start() + +starts the viewer. Once you start the viewer, the program is locked +until you exit the viewer. Usually, you want to make this the last +line in your code. + +After running, the following window appears + +.. image:: display_spherebox1.png + +Now, you can view your design. diff --git a/src/contrib/ccad/doc/trms.rst b/src/contrib/ccad/doc/trms.rst new file mode 100644 index 000000000..890d3afaf --- /dev/null +++ b/src/contrib/ccad/doc/trms.rst @@ -0,0 +1,85 @@ +Moving, Rotating, Mirroring, and Scaling +======================================== + +translate, rotate, mirror, and scale on shapes can be called with a +class method. For example:: + + s1.translate((1.0, 0.0, 0.0)) + +This translates *s1* in-place. ccad comes with a function call form +too with "ed" appended. For example:: + + s2 = ccad.translated(s1, (1.0, 0.0, 0.0)) + +The function form returns a new shape. *s2* is *s1* translated. *s1* +is unchanged. It can be useful when you want to save the old shape or +write code with fewer lines. + +In each of the following examples, **s1** describes the old object +drawn in **red**, and **s2** describes the new object drawn in **blue**. + +Moving +------ + +translate changes a shape's position but not its orientation. The +following example moves the box 2.0 in the x-direction, -6.0 in the +y-direction, and 4.0 in the z-direction:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.translated(s1, (2.0, -6.0, 4.0)) + +.. image:: trms_translate.png + +Rotation +-------- + +rotate rotates a shape about a point and a direction vector. Rotation +follows the right hand rule. The following example rotates the box 90 +degrees about the z-axis:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.rotated(s1, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), math.pi/2) + +.. image:: trms_rotate.png + +There are also three shorthand notations when rotation is about the +origin and a single axis: **rotatex**, **rotatey**, and **rotatez** +(or **rotatedx**, **rotatedy**, and **rotatedz** in function form). +The following example performs the same rotation above but in +shorthand:: + + s2 = ccad.rotatedz(math.pi/2) + +Mirroring +--------- + +mirror mirrors the shape about a point in a given direction. The +following example mirrors the box about the origin in the x-direction:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.mirrored(s1, (0.0, 0.0, 0.0), (1.0, 0.0, 0.0)) + +.. image:: trms_mirror.png + +Like rotate, shorthand versions exist of **mirrorx**, **mirrory**, and +**mirrorz** (or **mirroredx**, **mirroredy**, and **mirroredz** in +function form). + +Scaling +------- + +scale changes the size of the shape by scale factors. The following +example scales the box in the x-direction and then moves it:: + + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.scaled(s1, 2.0, 1.0, 1.0) + s2.translate((4.0, 0.0, 0.0)) + +.. image:: trms_scale.png + +Passing a single parameter to scale scales all three dimensions the +same. + +Like rotate and mirror, shorthand versions exist of **scalex**, +**scaley**, and **scalez** (or **scaledx**, **scaledy**, and +**scaledz** in function form). diff --git a/src/contrib/ccad/doc/trms_mirror.png b/src/contrib/ccad/doc/trms_mirror.png new file mode 100644 index 000000000..176d483f8 Binary files /dev/null and b/src/contrib/ccad/doc/trms_mirror.png differ diff --git a/src/contrib/ccad/doc/trms_rotate.png b/src/contrib/ccad/doc/trms_rotate.png new file mode 100644 index 000000000..96ab86fd6 Binary files /dev/null and b/src/contrib/ccad/doc/trms_rotate.png differ diff --git a/src/contrib/ccad/doc/trms_scale.png b/src/contrib/ccad/doc/trms_scale.png new file mode 100644 index 000000000..d1ebb8fd2 Binary files /dev/null and b/src/contrib/ccad/doc/trms_scale.png differ diff --git a/src/contrib/ccad/doc/trms_translate.png b/src/contrib/ccad/doc/trms_translate.png new file mode 100644 index 000000000..0eec70853 Binary files /dev/null and b/src/contrib/ccad/doc/trms_translate.png differ diff --git a/src/contrib/ccad/doc/using_display.rst b/src/contrib/ccad/doc/using_display.rst new file mode 100644 index 000000000..00af73fb3 --- /dev/null +++ b/src/contrib/ccad/doc/using_display.rst @@ -0,0 +1,100 @@ +Using Display +============= + +View -> Mode +------------ + +ccad offers three forms of viewing: wireframe, shaded (filled solids), +and hidden line removal. + +.. image:: display_wireframe.png +.. image:: display_shaded.png +.. image:: display_hlr.png + +View -> Side +------------ + +Various standard views can be selected from the View -> Side menu, or +you can use Blender hotkeys: + + - keypad 7 for top view + - keypad 1 for front view + - keypad 3 for right view + - hold shift with those keys to see the opposite view + +View -> Orbit +------------- + +Orbiting allows you to orbit around the scene, keeping the scene the +same distance from you. You can use the menus, you can press the +middle mouse button and move the mouse, or you can use Blender hotkeys: + + - keypad 8 to orbit up + - keypad 2 to orbit down + - keypad 4 to orbit left + - keypad 6 to orbit right + +View -> Pan +----------- + +Pan allows you to move the center of the scene. You can use the +menus, you can hold shift while pressing the middle mouse button and +moving the mouse, or you can use Blender hotkeys: + + - shift keypad 8 to pan up + - shift keypad 2 to pan down + - shift keypad 4 to pan left + - shift keypad 6 to pan right + + +View -> Zoom +------------ + +You can zoom in or out by using the menus, or using the Blender hotkeys: + + - keypad + to zoom in + - keypad - to zoom out + +Select -> Shape +--------------- + +In Select -> Shape mode, you select shapes. Move to the shape you are +interested in, and right click on it. The shape is selected. In this +case, shape refers to something you passed to the display function, +not any arbitrary shape. If you only passed one shape to the display +function, there is only one shape to select. + +Select -> Face +-------------- + +In Select -> Face mode, you can select a face on a shape you selected. +Right click on the face of interest, and you'll see the face index, +type, and center on the status line. + +.. image:: select_face.png + +Select -> Edge +-------------- + +In Select -> Edge mode, you can select an edge on a shape you +selected. Right click on the edge of interest, and you'll see the +edge index and center on the status line. + +.. image:: select_edge.png + +Edge selection can be very handy when trying to fillet specific edges. +Once you know the edge's coordinates (or even index if the shape won't +change), you can pass those coordinates to the fillet function. + +Select -> Vertex +---------------- + +In Select -> Vertex mode, you can select a vertex on a shape you +selected. Right click on the vertex of interest, and you'll see the +vertex index and center on the status line. + +.. image:: select_vertex.png + + + + diff --git a/src/contrib/ccad/doc/wire.png b/src/contrib/ccad/doc/wire.png new file mode 100644 index 000000000..3157df08e Binary files /dev/null and b/src/contrib/ccad/doc/wire.png differ diff --git a/src/contrib/ccad/doc/wire_helix.png b/src/contrib/ccad/doc/wire_helix.png new file mode 100644 index 000000000..4afa49dbc Binary files /dev/null and b/src/contrib/ccad/doc/wire_helix.png differ diff --git a/src/contrib/ccad/doc/wire_ngon.png b/src/contrib/ccad/doc/wire_ngon.png new file mode 100644 index 000000000..60b91efe0 Binary files /dev/null and b/src/contrib/ccad/doc/wire_ngon.png differ diff --git a/src/contrib/ccad/doc/wire_polygon.png b/src/contrib/ccad/doc/wire_polygon.png new file mode 100644 index 000000000..349a19edd Binary files /dev/null and b/src/contrib/ccad/doc/wire_polygon.png differ diff --git a/src/contrib/ccad/doc/wire_rectangle.png b/src/contrib/ccad/doc/wire_rectangle.png new file mode 100644 index 000000000..5b6c6aae2 Binary files /dev/null and b/src/contrib/ccad/doc/wire_rectangle.png differ diff --git a/src/contrib/ccad/model.py b/src/contrib/ccad/model.py new file mode 100644 index 000000000..eae36b631 --- /dev/null +++ b/src/contrib/ccad/model.py @@ -0,0 +1,2965 @@ +""" +Description +---------- +CCAD is a python text-based solid modeller and displayer based on pythonocc. +model.py contains classes and functions for modelling. + +Author +------ +Charles Sharman + +License +------- +Distributed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +View LICENSE for details. + +Philosophy +---------- + +1. Edges are continuous lines in 3D space. There should be no reason + to distinguish between them and OCC curves. Wires are collections + of edge connections. Faces are continuous surfaces in 3D space. + There should be no reason to distinguish between them and OCC + surfaces. Shells are collections of face connections. + +2. The end-user should not ever have to call pythonOCC directly. ccad + should handle all things someone might want to do in CAD. + +To Do +----- + +1. Allow shells (and 2D curves?) in the boolean operations. + +2. Allow shell returns from cylinder, sphere, box, wedge, cone, torus. + +3. More robust error handling--usually shows a buried SWIG issue, + which isn't helpful. + +4. Enhance the options to various routines to more encompass OCC's + capabilities. + +5. Add parabola, hyperbola edges + +6. Add edge intersection + +7. I never got OCC's concept of orientation in 3d. That caused a + liberal use of .fix statements. It would be nice to get this right. + +8. Distinction between face, wire, solid, etc. can get muddled after + certain operations. Boolean can naturally do so. Even a basic + translate converts TopoDS_Vertex to TopoDS_Shape, for example. + It's currently fixed by converting at the point where the specific + type is needed. May be better to correct immediately after the + loose operation. Ought to be more careful about this, or maybe + should make a single class (shape) that is smart and handles all + types. That seems harder but is more python-like. + +9. Separate compound, compsolid from solid. +""" + +from os import path as _path +import sys as _sys +import re as _re # Needed for svg +import math as _math + +#from OCC.ChFi3d import * +#from OCC.BlockFix import * +from OCC.Bnd import Bnd_Box as _Bnd_Box +#from OCC.BOP import * +from OCC.BRep import BRep_Builder as _BRep_Builder, BRep_Tool as _BRep_Tool +from OCC import BRepAlgo as _BRepAlgo +from OCC import BRepAlgoAPI as _BRepAlgoAPI +from OCC.BRepBndLib import BRepBndLib as _BRepBndLib +from OCC import BRepBuilderAPI as _BRepBuilderAPI +from OCC.BRepCheck import BRepCheck_Analyzer as _BRepCheck_Analyzer +from OCC.BRepFeat import BRepFeat_Gluer as _BRepFeat_Gluer +from OCC import BRepFilletAPI as _BRepFilletAPI +from OCC.BRepGProp import BRepGProp as _BRepGProp +from OCC import BRepOffsetAPI as _BRepOffsetAPI +from OCC import BRepPrimAPI as _BRepPrimAPI +from OCC import BRepTools as _BRepTools +from OCC.GC import (GC_MakeArcOfCircle as _GC_MakeArcOfCircle, + GC_MakeArcOfEllipse as _GC_MakeArcOfEllipse) +from OCC.GCPnts import (GCPnts_QuasiUniformDeflection as + _GCPnts_QuasiUniformDeflection) +from OCC.Geom import Geom_BezierCurve as _Geom_BezierCurve +from OCC import GeomAbs as _GeomAbs +from OCC.GeomAdaptor import (GeomAdaptor_Curve as _GeomAdaptor_Curve, + GeomAdaptor_Surface as _GeomAdaptor_Surface) +from OCC.GeomAPI import GeomAPI_PointsToBSpline as _GeomAPI_PointsToBSpline +from OCC import gp as _gp +from OCC.GProp import GProp_GProps as _GProp_GProps +from OCC import IFSelect as _IFSelect +from OCC.IGESControl import (IGESControl_Controller as _IGESControl_Controller, + IGESControl_Reader as _IGESControl_Reader, + IGESControl_Writer as _IGESControl_Writer) +from OCC.Interface import ( + Interface_Static_SetCVal as _Interface_Static_SetCVal, + Interface_Static_SetIVal as _Interface_Static_SetIVal, + Interface_Static_SetRVal as _Interface_Static_SetRVal) +from OCC.LocOpe import LocOpe_FindEdges as _LocOpe_FindEdges +from OCC.ShapeFix import ShapeFix_Shape as _ShapeFix_Shape +from OCC import STEPControl as _STEPControl +from OCC.StlAPI import StlAPI_Writer as _StlAPI_Writer +from OCC.TColgp import TColgp_Array1OfPnt as _TColgp_Array1OfPnt +from OCC.TColStd import TColStd_Array1OfReal as _TColStd_Array1OfReal +from OCC import TopAbs as _TopAbs +from OCC.TopoDS import (TopoDS_edge as _TopoDS_edge, + TopoDS_face as _TopoDS_face, + TopoDS_shell as _TopoDS_shell, + TopoDS_vertex as _TopoDS_vertex, + TopoDS_wire as _TopoDS_wire) +from OCC import TopoDS as _TopoDS +from OCC.TopExp import (TopExp_Explorer as _TopExp_Explorer, + TopExp_MapShapesAndAncestors as + _TopExp_MapShapesAndAncestors) +from OCC.TopOpeBRep import (TopOpeBRep_FacesIntersector as + _TopOpeBRep_FacesIntersector) +from OCC.TopOpeBRepTool import (TopOpeBRepTool_FuseEdges as + _TopOpeBRepTool_FuseEdges) +from OCC import TopTools as _TopTools + + +# Shape Functions +def _translate(s1, pdir): + m = _gp.gp_Trsf() + m.SetTranslation(_gp.gp_Vec(pdir[0], pdir[1], pdir[2])) + trf = _BRepBuilderAPI.BRepBuilderAPI_Transform(m) + trf.Perform(s1.shape, 1) + return trf.Shape() + + +def _rotate(s1, pabout, pdir, angle): + m = _gp.gp_Trsf() + m.SetRotation(_gp.gp_Ax1(_gp.gp_Pnt(pabout[0], pabout[1], pabout[2]), + _gp.gp_Dir(pdir[0], pdir[1], pdir[2])), angle) + trf = _BRepBuilderAPI.BRepBuilderAPI_Transform(m) + trf.Perform(s1.shape, 1) + return trf.Shape() + + +def _mirror(s1, pabout, pdir): + m = _gp.gp_Trsf() + m.SetMirror(_gp.gp_Ax2(_gp.gp_Pnt(pabout[0], pabout[1], pabout[2]), + _gp.gp_Dir(pdir[0], pdir[1], pdir[2]))) + trf = _BRepBuilderAPI.BRepBuilderAPI_Transform(m) + trf.Perform(s1.shape, 1) + return trf.Shape() + + +def _scale(s1, sx=1.0, sy=1.0, sz=1.0): + m = _gp.gp_GTrsf() + m.SetVectorialPart(_gp.gp_Mat(sx, 0, 0, 0, sy, 0, 0, 0, sz)) + trf = _BRepBuilderAPI.BRepBuilderAPI_GTransform(s1.shape, m, False) + return trf.Shape() + + +def translated(s1, pdir): + """ + Returns a new shape which is s1 translated (moved). + """ + s2 = s1.copy() + s2.translate(pdir) + return s2 + + +def rotated(s1, pabout, pdir, angle): + """ + Returns a new shape which is s1 rotated. + """ + s2 = s1.copy() + s2.rotate(pabout, pdir, angle) + return s2 + + +def rotatedx(s1, angle): + """ + Returns a new shape which is s1 rotated about (0.0, 0.0, 0.0) and + around (1.0, 0.0, 0.0) + """ + s2 = s1.copy() + s2.rotatex(angle) + return s2 + + +def rotatedy(s1, angle): + """ + Returns a new shape which is s1 rotated about (0.0, 0.0, 0.0) and + around (0.0, 1.0, 0.0) + """ + s2 = s1.copy() + s2.rotatey(angle) + return s2 + + +def rotatedz(s1, angle): + """ + Returns a new shape which is s1 rotated about (0.0, 0.0, 0.0) and + around (0.0, 0.0, 1.0) + """ + s2 = s1.copy() + s2.rotatez(angle) + return s2 + + +def mirrored(s1, pabout, pdir): + """ + Returns a new shape which is s1 mirrored. + """ + s2 = s1.copy() + s2.mirror(pabout, pdir) + return s2 + + +def mirroredx(s1): + """ + Returns a new shape which is s1 mirrored about (0.0, 0.0, 0.0) in + the x-direction + """ + s2 = s1.copy() + s2.mirrorx() + return s2 + + +def mirroredy(s1): + """ + Returns a new shape which is s1 mirrored about (0.0, 0.0, 0.0) in + the y-direction + """ + s2 = s1.copy() + s2.mirrory() + return s2 + + +def mirroredz(s1): + """ + Returns a new shape which is s1 mirrored about (0.0, 0.0, 0.0) in + the z-direction + """ + s2 = s1.copy() + s2.mirrorz() + return s2 + + +def scaled(s1, sfx, sfy=None, sfz=None): + """ + Returns a new shape which is s1 scaled by a different scale factor + in all 3 dimensions. If sfy and sfz are left undefined, all 3 + dimensions are scaled by sfx. + """ + s2 = s1.copy() + s2.scale(sfx, sfy, sfz) + return s2 + + +def scaledx(s1, sfx): + """ + Returns a new shape which is s1 scaled by sfx in the x-dimension + """ + s2 = s1.copy() + s2.scalex(sfx) + return s2 + + +def scaledy(s1, sfy): + """ + Returns a new shape which is s1 scaled by sfy in the y-dimension + """ + s2 = s1.copy() + s2.scaley(sfy) + return s2 + + +def scaledz(s1, sfz): + """ + Returns a new shape which is s1 scaled by sfz in the z-dimension + """ + s2 = s1.copy() + s2.scalez(sfz) + return s2 + + +def reversed(s1): + """ + Returns a new shape which is s1 reversed in orientation. + """ + s2 = s1.copy() + s2.reverse() + return s2 + + +# Face Functions +def _raw_faces_same_domain(f1, f2, skip_fits=0): + """ + If f1 and f2 are in the same domain, returns 1; otherwise, returns + 0. FacesIntersector is painfully slow. I don't think the + intersection calculation is necessary, but I couldn't find a + stand-alone OCC domain checker. *** + """ + # Didn't Work. Always empty. + #fi = TopOpeBRepDS_DataStructure() + #i1 = fi.AddShape(f1) + #print fi.ShapeSameDomain(f2).IsEmpty(): + + # Pre-screen, since FacesIntersector is slow + b1 = _BRep_Tool() + t1 = _GeomAdaptor_Surface(b1.Surface(_TopoDS_face(f1))).GetType() + t2 = _GeomAdaptor_Surface(b1.Surface(_TopoDS_face(f2))).GetType() + if t1 != t2: + return 0 + else: + if not skip_fits or (skip_fits and t1 <= _GeomAbs.GeomAbs_Torus): + fi = _TopOpeBRep_FacesIntersector() + fi.Perform(f1, f2) + return fi.SameDomain() + else: + return 0 + + +def _raw_faces_merge(f1, f2): + """ + Merges two raw faces in the same domain that share common edge(s) + into a single face. + """ + # Attempt with Fuse didn't work + #new_face = BRepAlgoAPI_Fuse(f1, f2).Shape() + #new_face = BRepAlgo_Fuse(f1, f2).Shape() + #print _raw_type(new_face) + + # Attempt with sewing didn't work + #b = BRepBuilderAPI_Sewing() + #b.Add(f1) + #b.Add(f2) + #b.Perform() + #new_face = b.SewedShape() + #print 'new_face type', _raw_type(new_face) + #new_faces[index] = new_face + + ## This worked, but only sometimes. Error + ## reporting wasn't sufficient enough to discover + ## cause. + #lfs = TopTools_ListOfShape() + #print f1.Orientation(), f2.Orientation() + # Didn't help + #if f1.Orientation() != f2.Orientation(): + # f2.Reverse() + #lfs.Append(f1) + #lfs.Append(f2) + #b = TopOpeBRepBuild_FuseFace(TopTools_ListOfShape(), lfs, 1) + ##b.PerformEdge() + #b.PerformFace() + #if not b.IsModified(): + # print 'Error: Face fusion failed' + # #return face(f1), face(f2) + #lfs = b.LFuseFace() + #new_face = lfs.First() + #new_faces[index] = new_face + + # The orientations were derived by trial and error. + # Expect problems. *** + other_wires = [] + ow1 = _BRepTools.BRepTools_OuterWire(_TopoDS_face(f1)) + ow1o = ow1.Orientation() + ex1w = _TopExp_Explorer(f1, _TopAbs.TopAbs_WIRE) + while ex1w.More(): + cw = ex1w.Current() + if cw != ow1: + cw.Orientation(_TopAbs.TopAbs_Compose(ow1o, cw.Orientation())) + other_wires.append(cw) + ex1w.Next() + ex1e = _BRepTools.BRepTools_WireExplorer(_TopoDS_wire(ow1)) + e1s = [] + while ex1e.More(): + e1s.append(ex1e.Current()) + ex1e.Next() + ow2 = _BRepTools.BRepTools_OuterWire(_TopoDS_face(f2)) + ow2o = ow2.Orientation() + ex2w = _TopExp_Explorer(f2, _TopAbs.TopAbs_WIRE) + while ex2w.More(): + cw = ex2w.Current() + if cw != ow2: + cw.Orientation(_TopAbs.TopAbs_Compose(ow2o, cw.Orientation())) + other_wires.append(cw) + ex2w.Next() + ex2e = _BRepTools.BRepTools_WireExplorer(_TopoDS_wire(ow2)) + e2s = [] + while ex2e.More(): + e2s.append(ex2e.Current()) + ex2e.Next() + # Find all places where wires are connected + c1s = [] + c2s = [] + e1_hashes = map(lambda x: x.__hash__(), e1s) + e2_hashes = map(lambda x: x.__hash__(), e2s) + for index1, e1 in enumerate(e1_hashes): + try: + index2 = e2_hashes.index(e1) + except: + index2 = -1 + if index2 > -1: + c1s.append(index1) + c2s.append(index2) + # Can only handle one continuous edge merge now. + # Multiple edge merges sometimes imply holes and + # sometimes don't. Truncate c1s, c2s + # accordingly. *** + if len(c1s) == 0: + print 'No common edges' + if len(c1s) > 1: + print 'c1-', c1s, c2s, len(e1s), len(e2s) + min_index = 0 + max_index = 0 + while (max_index < len(c1s)-1 and + c1s[max_index+1] - c1s[max_index] == 1): + max_index = max_index + 1 + if max_index < len(c1s)-1: + while (min_index > -(len(c1s)-1) and + c1s[min_index] - c1s[min_index-1] == 1): + min_index = min_index - 1 + if min_index < 0: + c1s = c1s[min_index:] + c1s[:max_index+1] + c2s = c2s[min_index:] + c2s[:max_index+1] + else: + c1s = c1s[:max_index+1] + c2s = c2s[:max_index+1] + print 'c1+', c1s, c2s, len(e1s), len(e2s) + # Create the merged wire + b = _BRepBuilderAPI.BRepBuilderAPI_MakeWire() + ds = [] + for count in range(len(e1s)): + if count in c1s: + if len(c2s) < len(e2s): # Make sure they're not all common + index1 = c1s.index(count) + count2 = c2s[index1] + while count2 in c2s: + count2 = (count2 + 1) % len(e2s) + b2 = _BRepBuilderAPI.BRepBuilderAPI_MakeWire() + while count2 not in c2s: + b2.Add(e2s[count2]) + count2 = (count2 + 1) % len(e2s) + b.Add(_TopoDS_wire(b2.Wire())) + else: + b.Add(e1s[count]) + ds.append(edge(e1s[count])) + w = b.Wire() + b = _ShapeFix_Shape(w) + b.Perform() + w = b.Shape() + # Create the fused face + brt = _BRep_Tool() + s = brt.Surface(_TopoDS_face(f1)) + bf = _BRepBuilderAPI.BRepBuilderAPI_MakeFace(s, _TopoDS_wire(w)) + for other_wire in other_wires: + if ow1o != ow2o: + other_wire.Reverse() + bf.Add(_TopoDS_wire(other_wire)) + f = bf.Face() + # Fix wire mess orientation mess ups. It would be + # nicer to avoid this in the first place + # above. *** + # ShapeFix creates new edges, which hinders + # multiple merges. Unfortunately, only an + # orientation fix had problems too. *** + b = _ShapeFix_Shape(f) + b.Perform() + f = b.Shape() + #b = ShapeFix_Face(f) + #bw = b.FixWireTool().GetObject() + #bw.FixReorder() + #b.FixOrientation() + #f = b.Face() + # Since I use f1's surface, I must orient + # the output the same way. + if ow1o == _TopAbs.TopAbs_REVERSED: + f.Reverse() + return f + + +# Solid Functions +def fuse(s1, s2, refine=0): + """ + Performs a boolean fuse between solids s1 and s2 and returns the + result as a new solid. + """ + #return solid(BRepAlgoAPI_Fuse(s1.shape, s2.shape).Shape()) + b1 = _BRepAlgoAPI.BRepAlgoAPI_Fuse(s1.shape, s2.shape) + if refine: + # Fuses edges along the way however doesn't fuse faces + b1.RefineEdges() + return solid(b1.Shape()) + + +def old_fuse(s1, s2): + """ + Performs a boolean fuse between solids s1 and s2 and returns the + result as a new solid. Uses OCC's old Fuse algorithm. + """ + return solid(_BRepAlgo.BRepAlgo_Fuse(s1.shape, s2.shape).Shape()) + + +def cut(s1, s2, refine=0): + """ + Performs a boolean cut between solids s1 and s2 and returns the + result as a new solid. + """ + b1 = _BRepAlgoAPI.BRepAlgoAPI_Cut(s1.shape, s2.shape) + if refine: + b1.RefineEdges() + return solid(b1.Shape()) + + +def old_cut(s1, s2): + """ + Performs a boolean cut between solids s1 and s2 and returns the + result as a new solid. Uses OCC's old Cut algorithm. + """ + return solid(_BRepAlgo.BRepAlgo_Cut(s1.shape, s2.shape).Shape()) + + +def common(s1, s2, refine=0): + """ + Performs a boolean common between solids s1 and s2 and returns the + result as a new solid. + """ + b1 = _BRepAlgoAPI.BRepAlgoAPI_Common(s1.shape, s2.shape) + if refine: + b1.RefineEdges() + return solid(b1.Shape()) + + +def old_common(s1, s2): + """ + Performs a boolean common between solids s1 and s2 and returns the + result as a new solid. Uses OCC's old Common algorithm. + """ + return solid(_BRepAlgo.BRepAlgo_Common(s1.shape, s2.shape).Shape()) + + +def _fillet_boolean(b1, rad): + new_edges = b1.SectionEdges() + b2 = _BRepFilletAPI.BRepFilletAPI_MakeFillet(b1.Shape()) + iterator = _TopTools.TopTools_ListIteratorOfListOfShape(new_edges) + while iterator.More(): + b2.Add(rad, _TopoDS_edge(iterator.Value())) + iterator.Next() + return solid(b2.Shape()) + + +def fillet_fuse(s1, s2, rad): + """ + Performs a boolean fuse between s1 and s2 and fillets the newly + created edges. + """ + if rad > 0.0: + return _fillet_boolean( + _BRepAlgoAPI.BRepAlgoAPI_Fuse(s1.shape, s2.shape), rad) + else: + return fuse(s1, s2) + + +def fillet_cut(s1, s2, rad): + """ + Performs a boolean cut between s1 and s2 and fillets the newly + created edges. + """ + if rad > 0.0: + return _fillet_boolean( + _BRepAlgoAPI.BRepAlgoAPI_Cut(s1.shape, s2.shape), rad) + else: + return cut(s1, s2) + + +def fillet_common(s1, s2, rad): + """ + Performs a boolean common between s1 and s2 and fillets the newly + created edges. + """ + if rad > 0.0: + return _fillet_boolean( + _BRepAlgoAPI.BRepAlgoAPI_Common(s1.shape, s2.shape), rad) + else: + return common(s1, s2) + + +def _chamfer_boolean(b1, dist): + # Doesn't work. The SectionEdges don't map to faces, it seems. *** + new_edges = b1.SectionEdges() + edge_map = _TopTools.TopTools_IndexedDataMapOfShapeListOfShape() + s = b1.Shape() + _TopExp_MapShapesAndAncestors(s, _TopAbs.TopAbs_EDGE, _TopAbs.TopAbs_FACE, + edge_map) + b2 = _BRepFilletAPI.BRepFilletAPI_MakeChamfer(s) + iterator = _TopTools.TopTools_ListIteratorOfListOfShape(new_edges) + while iterator.More(): + e1 = iterator.Value() + f1 = edge_map.FindFromKey(e1).First() + b2.Add(dist, dist, _TopoDS_edge(e1), _TopoDS_face(f1)) + iterator.Next() + return solid(b2.Shape()) + + +def chamfer_fuse(s1, s2, dist): + """ + Performs a chamfer fuse between s1 and s2 and chamfers the newly + created edges. + """ + if dist > 0.0: + return _chamfer_boolean( + _BRepAlgoAPI.BRepAlgoAPI_Fuse(s1.shape, s2.shape), dist) + else: + return fuse(s1, s2) + + +def chamfer_cut(s1, s2, dist): + """ + Performs a chamfer cut between s1 and s2 and chamfers the newly + created edges. + """ + if dist > 0.0: + return _chamfer_boolean( + _BRepAlgoAPI.BRepAlgoAPI_Cut(s1.shape, s2.shape), dist) + else: + return cut(s1, s2) + + +def chamfer_common(s1, s2, dist): + """ + Performs a chamfer common between s1 and s2 and chamfers the newly + created edges. + """ + if dist > 0.0: + return _chamfer_boolean( + _BRepAlgoAPI.BRepAlgoAPI_Common(s1.shape, s2.shape), dist) + else: + return common(s1, s2) + + +def glue(s1, s2, face_pairs=[]): + """ + Glues solids s1 and s2 together at the face_pairs. face_pairs is + a list of tuples. Each tuple represents face indices that should + be glued. + """ + s1f = s1._raw('face') + s2f = s2._raw('face') + if len(face_pairs) == 0: + print 'Error: Haven\'t implemented locate glued faces' + return + # This was expensive and didn't work. I believe intersections + # occurred at edge coincidences. I may want to try + # BRepTools_UVBounds as a pre-filter. I may also want to try + # Bounds first. *** + for i1, f1 in enumerate(s1f): + for i2, f2 in enumerate(s2f): + b = _TopOpeBRep_FacesIntersector() + b.Perform(f1, f2) + if b.SurfacesSameOriented(): + face_pairs.append((i1, i2)) + print face_pairs + + b = _BRepFeat_Gluer(s1.shape, s2.shape) + for face_pair in face_pairs: + f1 = _TopoDS_face(s1f[face_pair[0]]) + f2 = _TopoDS_face(s2f[face_pair[1]]) + b.Bind(f1, f2) + common_edges = _LocOpe_FindEdges(f1, f2) + common_edges.InitIterator() + while common_edges.More(): + b.Bind(common_edges.EdgeFrom(), common_edges.EdgeTo()) + common_edges.Next() + return solid(b.Shape()) + + +def simple_glue(s1, s2, face_pairs=[], tolerance=1e-3): + """ + Glues solids s1 and s2 together at the face_pairs. face_pairs is + a list of tuples. Each tuple represents faces that should be + glued. Unlike glue, each face_pair is expected to exactly + overlap. It's more robust than glue, so it was added. + """ + s1f = s1._raw('face') + s2f = s2._raw('face') + f1rs = [] + f2rs = [] + for face_pair in face_pairs: + f1rs.append(face_pair[0]) + f2rs.append(face_pair[1]) + f1rs.sort() + f1rs.reverse() + f2rs.sort() + f2rs.reverse() + for f1r in f1rs: + del s1f[f1r] + for f2r in f2rs: + del s2f[f2r] + b = _BRepBuilderAPI.BRepBuilderAPI_Sewing(tolerance) + for f in s1f: + b.Add(f) + for f in s2f: + b.Add(f) + b.Perform() + new_shell = b.SewedShape() + if _raw_type(new_shell) == 'shell': + b2 = _BRepBuilderAPI.BRepBuilderAPI_MakeSolid() + b2.Add(_TopoDS_shell(new_shell)) + return solid(b2.Solid()) + elif _raw_type(new_shell) == 'compound': + print 'Warning: simple_glue() returned compound' + s = solid(new_shell) + css = s._raw('shell') + c = _TopoDS.TopoDS_Compound() + b3 = _BRep_Builder() + b3.MakeCompound(c) + for cs in css: + b2 = _BRepBuilderAPI.BRepBuilderAPI_MakeSolid() + b2.Add(_TopoDS_shell(cs)) + b3.Add(c, b2.Solid()) + return solid(c) + else: + print 'Warning: Wrong sewed shape after simple_glue():', _raw_type(new_shell) + return solid(new_shell) + + +# Import Functions +def _convert_import(s): + stype = _raw_type(s) + if stype in ['solid', 'compound', 'compsolid']: + return solid(s) + elif stype == 'shape': + print 'Error: Unsupported type', stype + else: + return eval(stype + '(s)') + + +def from_brep(name): + """ + Imports a brep file and returns the shape. + """ + if _path.exists(name): + s = _TopoDS.TopoDS_Shape() + b = _BRep_Builder() + _BRepTools.BRepTools().Read(s, name, b) + return _convert_import(s) + else: + print 'Error: Can\'t find', name + + +def from_iges(name): + """ + Imports an iges file and returns the shape. + """ + if _path.exists(name): + reader = _IGESControl_Reader() + status = reader.ReadFile(name) + okay = reader.TransferRoots() + if not okay: + print 'Warning: Could not translate all shapes' + shape = reader.OneShape() + return _convert_import(shape) + else: + print 'Error: Can\'t find', name + + +def from_step(name): + """ + Imports a step file and returns the shape. + """ + if _path.exists(name): + reader = _STEPControl.STEPControl_Reader() + status = reader.ReadFile(name) + okay = reader.TransferRoots() + shape = reader.OneShape() + return _convert_import(shape) + else: + print 'Error: Can\'t find', name + + +def from_svg(name): + """ + Imports a 2D svg file, converts each graphics path into a wire, + and returns a list of wires. + + Warning: Currently only implements a subset of the svg protocol. + The subset follows. However, it's pretty easy to add more. + transforms with + matrix, translate + paths with + a, A, c, C, l, L, m, M, q, Q, z, Z elements + + Warning: Only checked with small inkscape-generated files + """ + + def finish_wire(): + if len(local_wire) > 0: + retval.append(wire(local_wire)) + # Cannot do local_wire = [] because thinks a new variable + del local_wire[:] + + def strpt_to_float(strpt): + pt = map(lambda x: float(x), strpt.split(',')) + if not absolute: + pt = (pt0[0] + pt[0], pt0[1] + pt[1]) # Make absolute + return (pt[0], pt[1]) + + def transform_matrix(): + retval = _gp.gp_Mat(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0) + for matrix in matrices: + retval.Multiply(matrix[1]) # second element is the matrix + return retval + + def transform_pts(transform, pts): + # Matrix transforms and adds a third dimension + retval = [] + for pt in pts: + #retval.append((pt[0], pt[1], 0.0)) # No transform (for debug) + xyz = _gp.gp_XYZ(pt[0], pt[1], 1.0) + xyz.Multiply(transform) + # svg y increases downward; height - xpt[1] corrects + retval.append((xyz.X(), height - xyz.Y(), 0.0)) + return retval + + def vector_angle(u, v): + # Computes the angle between two vectors + if (u[0] * v[1] - u[1] * v[0]) < 0.0: + s = -1 + else: + s = 1 + dot = u[0]*v[0] + u[1]*v[1] + magu = _math.sqrt(u[0]**2 + u[1]**2) + magv = _math.sqrt(v[0]**2 + v[1]**2) + return s * _math.acos(min(1.0, max(-1.0, dot / (magu * magv)))) + + height = 0.0 + fp = open(name, 'r') + entities = [] + matrices = [] + transforms = [] + paths = [] + for line in fp: + m = _re.match('\s*<([a-zA-Z]+)', line) # Start of an entity + if m: + entities.append(m.group(1)) + if len(entities) > 0 and entities[-1] == 'svg': + m = _re.match('\s*height="(.+)"', line) + if m: + height = float(m.group(1)) + if len(entities) > 1 and entities[-2] == 'g' and entities[-1] == 'path': + m = _re.match('\s*d="(.+)"', line) # Should this be multi-line? + if m: + paths.append(m.group(1)) + transforms.append(transform_matrix()) + if len(entities) > 0 and entities[-1] == 'g': + m = _re.match('\s*transform="matrix\((.+)\)"', line) + if m: + matrix = map(lambda x: float(x), m.group(1).split(',')) + matrices.append((len(entities), _gp.gp_Mat(matrix[0], matrix[2], matrix[4], matrix[1], matrix[3], matrix[5], 0.0, 0.0, 1.0))) + m = _re.match('\s*transform="translate\((.+)\)"', line) + if m: + matrix = map(lambda x: float(x), m.group(1).split(',')) + matrices.append((len(entities), _gp.gp_Mat(1.0, 0.0, matrix[0], 0.0, 1.0, matrix[1], 0.0, 0.0, 1.0))) + m = _re.match('.*/>|.* 0 and matrices[-1][0] == len(entities): + matrices.pop() + entities.pop() + fp.close() + + retval = [] + local_wire = [] + cmds = 'aAcChHlLmMqQsStTvVzZ' + + for path, transform in zip(paths, transforms): + pt0 = (0.0, 0.0) + absolute = 1 + #ls = filter(lambda x: x != '', re.split('([' + cmds + '])', path)) # command, arguments format + ls = path.split() + index = 0 + while index < len(ls): + + if ls[index].isupper(): + absolute = 1 + else: + absolute = 0 + + cmd = ls[index].lower() + + if cmd == 'm': # Move + finish_wire() + pt0 = strpt_to_float(ls[index + 1]) + index = index + 2 + pts = [pt0] + while index < len(ls) and ls[index] not in cmds: + pts.append(strpt_to_float(ls[index])) + pt0 = pts[-1] + index = index + 1 + if len(pts) > 1: + local_wire.append(polygon(transform_pts(transform, pts))) + + elif cmd == 'q': # Quadratic Bezier + index = index + 1 + while index < len(ls) and ls[index] not in cmds: + pts = [pt0, + strpt_to_float(ls[index]), + strpt_to_float(ls[index + 1])] + pt0 = pts[-1] + local_wire.append(bezier(transform_pts(transform, pts))) + index = index + 2 + + elif cmd == 'c': # Cubic Bezier + index = index + 1 + while index < len(ls) and ls[index] not in cmds: + pts = [pt0, + strpt_to_float(ls[index]), + strpt_to_float(ls[index + 1]), + strpt_to_float(ls[index + 2])] + pt0 = pts[-1] + local_wire.append(bezier(transform_pts(transform, pts))) + index = index + 3 + + elif cmd == 'l': # Line + index = index + 1 + pts = [pt0] + while index < len(ls) and ls[index] not in cmds: + pts.append(strpt_to_float(ls[index])) + pt0 = pts[-1] + index = index + 1 + local_wire.append(polygon(transform_pts(transform, pts))) + + elif cmd == 'a': # Elliptical Arc + index = index + 1 + while index < len(ls) and ls[index] not in cmds: + x1, y1 = pt0 + rx, ry = map(lambda x: float(x), ls[index].split(',')) + phi = _math.radians(float(ls[index + 1])) + fa = int(ls[index + 2]) + fs = int(ls[index + 3]) + x2, y2 = strpt_to_float(ls[index + 4]) + index = index + 5 + + # Algorithm copied from W3C SVG 1.1 Appendix F + rx = abs(rx) + ry = abs(ry) + if rx == 0.0 or ry == 0.0: + pts = [(x1, y1), (x2, y2)] + local_wire.append(polygon(transform_pts(transform, pts))) + else: + cosphi = _math.cos(phi) + sinphi = _math.sin(phi) + x = (x1-x2)/2 + y = (y1-y2)/2 + x1p = cosphi*x + sinphi*y + y1p = -sinphi*x + cosphi*y + # Correct for out-of-range radii + l = _math.sqrt((x1p**2) / (rx**2) + (y1p**2) / (ry**2)) + if l > 1.0: + rx = rx * l + ry = ry * l + + if fa == fs: + s = -1 + else: + s = 1 + c = s * _math.sqrt(max(0.0, (rx**2)*(ry**2) - (rx**2)*(y1p**2) - (ry**2)*(x1p**2)) / ((rx**2)*(y1p**2) + (ry**2)*(x1p**2))) + cxp = c * rx*y1p/ry + cyp = c * (-ry)*x1p/rx + cx = cosphi*cxp - sinphi*cyp + (x1+x2)/2 + cy = sinphi*cxp + cosphi*cyp + (y1+y2)/2 + v1 = (1.0, 0.0) + v2 = ((x1p-cxp)/rx, (y1p-cyp)/ry) + v3 = ((-x1p-cxp)/rx, (-y1p-cyp)/ry) + theta = vector_angle(v1, v2) + dtheta = vector_angle(v2, v3) % (2 * _math.pi) + if fs == 0 and dtheta > 0.0: + dtheta = dtheta - 2 * _math.pi + elif fs == 1 and dtheta < 0.0: + dtheta = dtheta + 2 * _math.pi + if dtheta < 0.0: + theta1 = theta + dtheta + theta2 = theta + else: + theta1 = theta + theta2 = theta + dtheta + a = translate(rotatez(arc_ellipse(rx, ry, theta1, theta2), phi), (cx, cy, 0.0)) + a.bounds() + # Transform a + m = _gp.gp_Trsf() + # There's probably a better way to convert a matrix to a transformation + m.SetValues(transform.Value(1, 1), transform.Value(1, 2), 0.0, transform.Value(1, 3), + transform.Value(2, 1), transform.Value(2, 2), 0.0, transform.Value(2, 3), + 0.0, 0.0, transform.Value(1, 1), 0.0, 1e-16, 1e-7) # unsure of TolAng and TolDist + trf = _BRepBuilderAPI.BRepBuilderAPI_Transform(m) + trf.Perform(a.shape, 1) + a.shape = trf.Shape() + # Convert y to height - y + a.mirrory() + a.translate((0.0, height, 0.0)) + local_wire.append(a) + + pt0 = (x2, y2) + + elif cmd == 'z': # Close path + finish_wire() + index = index + 1 + + elif cmd in cmds: # Need to do these some time + print 'Error:', cmd, 'not implemented in path:', path + _sys.exit() + + else: + print 'Error: svg path type unknown', cmd + _sys.exit() + + finish_wire() + return retval + + +def _raw_type(raw_shape): + raw_types = {_TopAbs.TopAbs_COMPOUND: 'compound', + _TopAbs.TopAbs_COMPSOLID: 'compsolid', + _TopAbs.TopAbs_SOLID: 'solid', + _TopAbs.TopAbs_SHELL: 'shell', + _TopAbs.TopAbs_FACE: 'face', + _TopAbs.TopAbs_WIRE: 'wire', + _TopAbs.TopAbs_EDGE: 'edge', + _TopAbs.TopAbs_VERTEX: 'vertex', + _TopAbs.TopAbs_SHAPE: 'shape'} + try: + return raw_types[raw_shape.ShapeType()] + except: + return 'unknown' + +# Classes +class shape(object): + """ + A base class for all shapes: edge, wire, face, shell, solid. + """ + + def _raw_type(self): + return _raw_type(self.shape) + + def to_brep(self, name): + """ + Exports the shape in .brep format + """ + _BRepTools.BRepTools().Write(self.shape, name) + + def to_iges(self, name, **options): + """ + Exports the shape in .igs format. It supports the following options: + + precision_mode: + -1: uncertainty is set to the minimum tolerance of all shapes + 0 (Default): uncertainty is set to the average tolerance of all + shapes + 1: uncertainty is set to the maximum tolerance of all shapes + 2: uncertainty is set to precision_value + + precision_value (0.0001 Default): for precision_mode 2, uncertainty is + set to this + + brep_mode: + 0 (Default): faces translated to IGES 144 entities (no brep) + 1: faces translated to IGES 510 entities (will have brep) + + convert_surface_mode: + 0 (Default): elementary surfaces are written as surfaces of + revolution + 1: elementary surfaces are converted to corresponding IGES surfaces + + units: + 'MM': millimeters + 'INCH': inches + + author: the author of the file + + sending_company: + + receiving_company: + + product: the product creating the file + """ + + # Setup + c = _IGESControl_Controller() + c.Init() + if 'units' in options: + units = options['units'] + else: + units = 'MM' + if 'brep_mode' in options: + brep_mode = options['brep_mode'] + else: + brep_mode = 0 + w = _IGESControl_Writer(units, brep_mode) + + # Parse Options + if 'precision_mode' in options: + _Interface_Static_SetIVal('write.precision.mode', options['precision_mode']) + if 'precision_value' in options: + _Interface_Static_SetRVal('write.precision.val', options['precision_value']) + #if 'brep_mode' in options: + # # Didn't work here + # _Interface_Static_SetIVal('write.iges.brep.mode', options['brep_mode']) + if 'convert_surface_mode' in options: + if options['convert_surface_mode'] == 1: + value = 'On' + else: + value = 'Off' + _Interface_Static_SetCVal('write.convertsurface.mode', value) + #if 'units' in options: + # _Interface_Static_SetCVal('write.step.unit', options['units']) + if 'author' in options: + _Interface_Static_SetCVal('write.iges.header.author', options['author']) + if 'sending_company' in options: + _Interface_Static_SetCVal('write.iges.header.company', options['sending_company']) + if 'receiving_company' in options: + _Interface_Static_SetCVal('write.iges.header.receiver', options['receiveing_company']) + if 'product' in options: + _Interface_Static_SetCVal('write.iges.header.product', options['product']) + + # Write + okay = w.AddShape(self.shape) + if not okay: + print 'Warning: Could not translate all shapes' + w.Write(name) + + def to_step(self, name, **options): + """ + Exports the shape in .stp format. It supports the following options: + + precision_mode: + -1: uncertainty is set to the minimum tolerance of all shapes + 0 (Default): uncertainty is set to the average tolerance of all + shapes + 1: uncertainty is set the the maximum tolerance of all shapes + 2: uncertainty is set to precision_value + + precision_value (0.0001 Default): for precision_mode 2, uncertainty is + set to this + + assembly: + 0 (Default): writes without assemblies + 1: writes with assemblies + 2: TopoDS_Compounds are written as assemblies + + schema: defines the version of schema + 1 (Default): AP214CD + 2: AP214DIS + 3: AP203 + 4: AP214IS + + surface_curve_mode: + 0: write without pcurves + 1 (Default): write with pcurves + + transfer_mode: + 0 (Default): automatic + 1: transfer to manifold solid brep + 2: transfer to faceted brep (only for planar faces and linear + edges) + 3: transfer to shell based surface model + 4: transfer to geometric curve set + + units: + 'MM': millimeters + 'INCH': inches + + product: the product creating the file + """ + + # Setup + c = _STEPControl.STEPControl_Controller() + c.Init() + w = _STEPControl.STEPControl_Writer() + + # Parse Options + if 'precision_mode' in options: + _Interface_Static_SetIVal('write.precision.mode', options['precision_mode']) + if 'precision_value' in options: + _Interface_Static_SetRVal('write.precision.val', options['precision_value']) + if 'assembly' in options: + _Interface_Static_SetIVal('write.step.assembly', options['assembly']) + if 'schema' in options: + _Interface_Static_SetCVal('write.step.schema', str(options['schema'])) + w.Model(True) + if 'product' in options: + _Interface_Static_SetCVal('write.product.name', options['product']) + if 'surface_curve_mode' in options: + _Interface_Static_SetIVal('write.surfacecurve.mode', options['surface_curve_mode']) + if 'units' in options: + _Interface_Static_SetCVal('write.step.unit', options['units']) + if 'transfer_mode' in options: + transfer_mode = [_STEPControl.STEPControl_AsIs, + _STEPControl.STEPControl_ManifoldSolidBrep, + _STEPControl.STEPControl_FacetedBrep, + _STEPControl.STEPControl_ShellBasedSurfaceModel, + _STEPControl.STEPControl_GeometricCurveSet][options['transfer_mode']] + else: + transfer_mode = _STEPControl.STEPControl_AsIs + + # Write + okay = w.Transfer(self.shape, transfer_mode) + if okay in [_IFSelect.IFSelect_RetError, + _IFSelect.IFSelect_RetFail, + _IFSelect.IFSelect_RetStop]: + print 'Error: Could not translate shape to step' + else: + w.Write(name) + + def translate(self, pdir): + """ + moves the shape + """ + self.shape = _translate(self, pdir) + + def rotate(self, pabout, pdir, angle): + """ + rotates the shape + """ + self.shape = _rotate(self, pabout, pdir, angle) + + def rotatex(self, angle): + """ + rotates the shape about (0.0, 0.0, 0.0) around (1.0, 0.0, 0.0) + """ + self.shape = _rotate(self, (0.0, 0.0, 0.0), (1.0, 0.0, 0.0), angle) + + def rotatey(self, angle): + """ + rotates the shape about (0.0, 0.0, 0.0) around (0.0, 1.0, 0.0) + """ + self.shape = _rotate(self, (0.0, 0.0, 0.0), (0.0, 1.0, 0.0), angle) + + def rotatez(self, angle): + """ + rotates the shape about (0.0, 0.0, 0.0) around (1.0, 0.0, 0.0) + """ + self.shape = _rotate(self, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), angle) + + def mirror(self, pabout, pdir): + """ + mirrors the shape + """ + self.shape = _mirror(self, pabout, pdir) + + def mirrorx(self): + """ + mirrors the shape about (0.0, 0.0, 0.0) in the x-direction + """ + self.shape = _mirror(self, (0.0, 0.0, 0.0), (1.0, 0.0, 0.0)) + + def mirrory(self): + """ + mirrors the shape about (0.0, 0.0, 0.0) in the y-direction + """ + self.shape = _mirror(self, (0.0, 0.0, 0.0), (0.0, 1.0, 0.0)) + + def mirrorz(self): + """ + mirrors the shape about (0.0, 0.0, 0.0) in the z-direction + """ + self.shape = _mirror(self, (0.0, 0.0, 0.0), (0.0, 0.0, 1.0)) + + def scale(self, sfx, sfy=None, sfz=None): + """ + scales the shape by scale factor sfx in all 3 dimensions. If + all 3 scale factors are specified, scales x, y, z + independently. + """ + if sfy and sfz: + self.shape = _scale(self, sfx, sfy, sfz) + else: + self.shape = _scale(self, sfx, sfx, sfx) + + def scalex(self, sfx): + """ + scales the shape by scale factor sfx in the x-dimension + """ + self.shape = _scale(self, sfx, 1.0, 1.0) + + def scaley(self, sfy): + """ + scales the shape by scale factor sfy in the y-dimension + """ + self.shape = _scale(self, 1.0, sfy, 1.0) + + def scalez(self, sfz): + """ + scales the shape by scale factor sfz in the z-dimension + """ + self.shape = _scale(self, 1.0, 1.0, sfz) + + def reverse(self): + """ + Reverses a shape's orientation + """ + self.shape.Reverse() + + def _raw(self, raw_type): + """ + Returns a list of all the OCC vertices, edges, wires, faces, + shells, or solids (dependent on raw_type) in the shape. + """ + raw_types = {'vertex': _TopAbs.TopAbs_VERTEX, + 'edge': _TopAbs.TopAbs_EDGE, + 'wire': _TopAbs.TopAbs_WIRE, + 'face': _TopAbs.TopAbs_FACE, + 'shell': _TopAbs.TopAbs_SHELL, + 'solid': _TopAbs.TopAbs_SOLID} + # This returns OCC types, not ccad types + if self.stype == 'wire' and raw_type == 'edge': + ex = _BRepTools.BRepTools_WireExplorer(_TopoDS_wire(self.shape)) # Ordered this way + else: + ex = _TopExp_Explorer(self.shape, raw_types[raw_type]) + hashes = [] + retval = [] + while ex.More(): + s1 = ex.Current() + s1_hash = s1.__hash__() + if s1_hash not in hashes: + hashes.append(s1_hash) + retval.append(s1) + ex.Next() + return retval + + def _valid_subshapes(self, include_top = False): + types = ['vertex', 'edge', 'wire', 'face', 'shell', 'solid'] + self_index = types.index(self.stype) + if include_top: + self_index = self_index + 1 + return types[:self_index] + + def _valid_subshape(self, stype): + if stype in self._valid_subshapes(): + return True + else: + print 'Warning: ' + stype + ' is not a subshape of ' + self.stype + return False + + def subshapes(self, stype): + """ + Returns a list of all the vertices, edges, wires, faces, + shells, or solids (dependent on stype) in the shape. + """ + retval = [] + if self._valid_subshape(stype): + raw_shapes = self._raw(stype) + for raw_shape in raw_shapes: + retval.append(eval(stype + '(raw_shape)')) + return retval + + def copy(self): + """ + Copies the shape and returns the copied shape + """ + s = _BRepBuilderAPI.BRepBuilderAPI_Copy(self.shape).Shape() + return eval(self.stype + '(s)') + + def bounds(self): + """ + Puts a box around the shape and returns the minimum and + maximum coordinates in a 6-tuple. + + It currently returns a box which extends far beyond the real + boundaries. It seems to be an OCC problem, but uncertain *** + """ + b1 = _Bnd_Box() + b2 = _BRepBndLib() + b2.Add(self.shape, b1) + return b1.Get() + + def center(self): + """ + Finds the center of mass of the shape. + """ + print 'Center not defined for', self.stype + _sys.exit() + + def subcenters(self, stype): + """ + Iterates over every subshape (as selected by stype) and + returns the center of each subshape. For example, + s.subcenters('face') finds the centers of the faces of the + shape. + """ + centers = [] + if self._valid_subshape(stype): + ss = self._raw(stype) + for s in ss: + sshape = eval(stype + '(s)') + centers.append(sshape.center()) + return centers + + def check(self): + """ + Performs a BRep check. Returns 1 if its okay. Returns 0 + otherwise. I'd like to generate a report on 0, but it + requires an SStream, which pythonocc doesn't seem to handle + right now. *** + """ + b = _BRepCheck_Analyzer(self.shape) + return b.IsValid() + + def fix(self, precision=None, max_tolerance=None, min_tolerance=None): + """ + Performs Shape Healing. It didn't work on all cases I + tested. Perhaps it needs more help (precision and + tolerance)? *** + """ + b = _ShapeFix_Shape(self.shape) + if precision is not None: + b.SetPrecision(precision) + if max_tolerance is not None: + b.SetMaxTolerance(max_tolerance) + if min_tolerance is not None: + b.SetMinTolerance(min_tolerance) + b.Perform() + self.shape = b.Shape() + + def dump(self, flat = True, _level = 0): + """ + Print the details of an object from the top down. + + If flat is False, dumps a hierarchy form. + _level is used for the recursion; don't touch it. + """ + types = self._valid_subshapes() + types.reverse() + if not flat and len(types) > 0: + types = [types[0]] + for t in types: + ss = self.subshapes(t) + for count, s in enumerate(ss): + x, y, z = s.center() + if t in ['vertex', 'edge', 'face']: + suffix = ' tolerance: %.4e' % s.tolerance() + else: + suffix = '' + print '.'*_level + '%s%d location: (%.6f,%.6f,%.6f)%s' % (t, count, x, y, z, suffix) + if not flat: + s.dump(False, level + 1) + + def nearest(self, stype, positions, eps=1e-12): + """ + Returns the index of the subshape nearest each position in + positions. If more than one shape tie for nearest, a 4th + argument in positions selects which item to choose. + + This algorithm could probably be improved *** + """ + shape_centers = self.subcenters(stype) + shape_indices = [] + for pt in positions: + min_dsq = (pt[0]-shape_centers[0][0])**2 + (pt[1]-shape_centers[0][1])**2 + (pt[2]-shape_centers[0][2])**2 + arg_min = 0 + arg_mins = [] + for count in range(1, len(shape_centers)): + shape_center = shape_centers[count] + dsq = (pt[0]-shape_center[0])**2 + (pt[1]-shape_center[1])**2 + (pt[2]-shape_center[2])**2 + de = dsq - min_dsq + if de < 0.0: + if -de < eps: + arg_mins.append(arg_min) + else: + arg_mins = [] + min_dsq = dsq + arg_min = count + elif de < eps: + arg_mins.append(count) + if len(pt) == 4 and len(arg_mins) > 0: + shape_indices.append(arg_mins[pt[3]-2]) + else: + shape_indices.append(arg_min) + return shape_indices + + def subtolerance(self, stype='all', ttype='all'): + """ + Iterates through every vertex, edge, and face, + recording the tolerance. Returns the minimum, average, and + maximum tolerance. + + stype limits the sweep to shapes of type stype. + + ttype limits the tolerance type to 'min', 'average', or 'max'. + """ + + tolerances = [] + + # Vertices + if stype == 'vertex' or stype == 'all': + raw_shapes = self._raw('vertex') + for raw_shape in raw_shapes: + tolerances.append(_BRep_Tool.Tolerance(_TopoDS_vertex(raw_shape))) + + # Edges + if stype == 'edge' or stype == 'all': + raw_shapes = self._raw('edge') + for raw_shape in raw_shapes: + tolerances.append(_BRep_Tool.Tolerance(_TopoDS_edge(raw_shape))) + + # Faces + if stype == 'face' or stype == 'all': + raw_shapes = self._raw('face') + for raw_shape in raw_shapes: + tolerances.append(_BRep_Tool.Tolerance(_TopoDS_face(raw_shape))) + + min_tol = min(tolerances) + ave_tol = sum(tolerances) / len(tolerances) + max_tol = max(tolerances) + + retval = (min_tol, ave_tol, max_tol) + + if ttype == 'all': + return retval + else: + return retval[['min', 'average', 'max'].index(ttype)] + + +class vertex(shape): + """ + A point in 3d space + """ + + stype = 'vertex' + + def __init__(self, s): + if hasattr(s, '__getitem__') and isinstance(s[0], (int, float)): + b = _BRepBuilderAPI.BRepBuilderAPI_MakeVertex( + _gp.gp_Pnt(s[0], s[1], s[2])) + self.shape = b.Vertex() + elif isinstance(s, _TopoDS.TopoDS_Vertex): + self.shape = s + elif isinstance(s, _TopoDS.TopoDS_Shape) and _raw_type(s) == 'vertex': + self.shape = _TopoDS_vertex(s) + else: + raise TypeError + + def center(self): + p = _BRep_Tool.Pnt(_TopoDS_vertex(self.shape)) + return (p.X(), p.Y(), p.Z()) + + def tolerance(self): + return _BRep_Tool.Tolerance(self.shape) + + +class edge(shape): + """ + A continuous curve in 3d space bounded by two end points. + """ + + stype = 'edge' + + def __init__(self, s): + if isinstance(s, _TopoDS.TopoDS_Edge): + self.shape = s + elif isinstance(s, _TopoDS.TopoDS_Shape) and _raw_type(s) == 'edge': + self.shape = _TopoDS_edge(s) + else: + raise TypeError + + def center(self): + g1 = _GProp_GProps() + g2 = _BRepGProp() + g2.LinearProperties(self.shape, g1) + p = g1.CentreOfMass() + return (p.X(), p.Y(), p.Z()) + + def length(self): + """ + Returns the length of the edge + """ + g1 = _GProp_GProps() + g2 = _BRepGProp() + g2.LinearProperties(self.shape, g1) + return g1.Mass() + + def tolerance(self): + return _BRep_Tool.Tolerance(_TopoDS_edge(self.shape)) + + def type(self): + """ + Returns the type of curve the edge is part of. Use sparingly. + The GeomAdaptor_Curve call often caused a Segmentation fault. *** + """ + b1 = _BRep_Tool() + c1 = b1.Curve(_TopoDS_edge(self.shape)) + gac1 = _GeomAdaptor_Curve(c1[0], c1[1], c1[2]) + t1 = gac1.GetType() + return {_GeomAbs.GeomAbs_Line: 'line', + _GeomAbs.GeomAbs_Circle: 'circle', + _GeomAbs.GeomAbs_Ellipse: 'ellipse', + _GeomAbs.GeomAbs_Hyperbola: 'hyperbola', + _GeomAbs.GeomAbs_Parabola: 'parabola', + _GeomAbs.GeomAbs_BezierCurve: 'bezier', + _GeomAbs.GeomAbs_BSplineCurve: 'bspline', + _GeomAbs.GeomAbs_OtherCurve: 'other'}[t1] + + def poly(self, deflection=1e-3): + """ + Returns a polyline approximation to the edge + """ + c1 = _BRep_Tool.Curve(_TopoDS_edge(self.shape)) + gac1 = _GeomAdaptor_Curve(c1[0], c1[1], c1[2]) + ud = _GCPnts_QuasiUniformDeflection(gac1, deflection) + retval = [] + for count in range(1, ud.NbPoints()+1): + pt = gac1.Value(ud.Parameter(count)) + retval.append((pt.X(), pt.Y(), pt.Z())) + return retval + + +class wire(shape): + """ + A connection of edges. Can be instantiated with a list of edges + to connect. + """ + + stype = 'wire' + + def __init__(self, es): + if isinstance(es, (list, tuple)): + b = _BRepBuilderAPI.BRepBuilderAPI_MakeWire() + for e in es: + if e.stype == 'edge': + b.Add(_TopoDS_edge(e.shape)) + elif e.stype == 'wire': + b.Add(_TopoDS_wire(e.shape)) + else: + print 'Error: Cannot add', e.stype, 'to wire.' + self.shape = b.Wire() + elif isinstance(es, _TopoDS.TopoDS_Wire): + self.shape = es + elif isinstance(es, _TopoDS.TopoDS_Shape) and _raw_type(es) == 'wire': + self.shape = _TopoDS_wire(es) + else: + raise TypeError + + def center(self): + subs = self.subshapes('edge') + c = (0.0, 0.0, 0.0) + total_length = 0.0 + for sub in subs: + sub_center = sub.center() + length = sub.length() + c = (c[0] + length * sub_center[0], + c[1] + length * sub_center[1], + c[2] + length * sub_center[2]) + total_length = total_length + length + c = (c[0] / total_length, c[1] / total_length, c[2] / total_length) + return c + + def length(self): + """ + Returns the length of the wire + """ + subs = self.subshapes('edge') + total_length = 0.0 + for sub in subs: + length = sub.length() + total_length = total_length + length + return total_length + + def poly(self, deflection=1e-3): + """ + Returns a polyline approximation to the wire. + """ + wo = self.shape.Orientation() + edges = self.subshapes('edge') + retval = [] + for edge in edges: + ep = edge.poly(deflection) + if edge.shape.Orientation() != wo: + ep = ep[::-1] + retval = retval + ep[:-1] + retval = retval + [ep[-1]] + return retval + + +class face(shape): + """ + A continuous surface in 3d space bounded by a closed wire. + """ + + stype = 'face' + + def __init__(self, s): + if isinstance(s, _TopoDS.TopoDS_Face): + self.shape = s + elif isinstance(s, _TopoDS.TopoDS_Shape) and _raw_type(s) == 'face': + self.shape = _TopoDS_face(s) + else: + raise TypeError + + def fillet(self, rad, vertex_indices=None): + """ + Fillets the face at specified vertices with specified radii. + + If rad is a float, fillets all edges the same radius. rad may + also be a list of [(rad1, vertex_indices1), (rad2, + vertex_indices2), ...] where rad1 is the radius to fillet all + vertices in vertex_indices1, rad2 is the radius to fillet all + vertices in vertex_indices2, etc. In this latter case, the + second argument (vertex_indices) is not used. + + If vertex_indices is None, fillets all vertices. + vertex_indices may also be a list of [(x1, y1, z1), (x2, y2, + z2), ...] where each (x1, y1, z1) specifices the edge with + center nearest that point. + """ + + raw_vertices = self._raw('vertex') + if isinstance(rad, (int, float)): + # Make real vertex_indices + if vertex_indices is None: + vertex_indices = range(len(raw_vertices)) + if len(vertex_indices) <= 0: + return + if not isinstance(vertex_indices[0], int): # coordinate positions + vertex_indices = self.nearest('vertex', vertex_indices) + #print 'vertex_indices', vertex_indices + fillet_rads = [(rad, vertex_indices)] + else: + fillet_rads = rad + + b = _BRepFilletAPI.BRepFilletAPI_MakeFillet2d(_TopoDS_face(self.shape)) + changed = 0 + for fillet_rad in fillet_rads: + rad, vertex_indices = fillet_rad + if rad > 0.0: + for vertex_index in vertex_indices: + changed = 1 + b.AddFillet(_TopoDS_vertex(raw_vertices[vertex_index]), rad) + if changed: + self.shape = b.Shape() + + def wire(self): + """ + Returns the outside of the face as a wire + """ + #return wire(self._raw('wire')[0]) + return wire(_BRepTools.BRepTools_OuterWire(_TopoDS_face(self.shape))) + + def inner_wires(self): + """ + Returns the inner contours of a face as a list of wires + """ + ow1 = _BRepTools.BRepTools_OuterWire(_TopoDS_face(self.shape)) + ow1o = ow1.Orientation() + ex1w = _TopExp_Explorer(self.shape, _TopAbs.TopAbs_WIRE) + retval = [] + while ex1w.More(): + cw = ex1w.Current() + if cw != ow1: + cw.Orientation(_TopAbs.TopAbs_Compose(ow1o, cw.Orientation())) + retval.append(wire(cw)) + ex1w.Next() + return retval + + def center(self): + g1 = _GProp_GProps() + g2 = _BRepGProp() + g2.SurfaceProperties(self.shape, g1) + p = g1.CentreOfMass() + return (p.X(), p.Y(), p.Z()) + + def area(self): + """ + Returns the area of the face + """ + g1 = _GProp_GProps() + g2 = _BRepGProp() + g2.SurfaceProperties(self.shape, g1) + return g1.Mass() + + def tolerance(self): + return _BRep_Tool.Tolerance(_TopoDS_face(self.shape)) + + def type(self): + """ + Returns the type of surface the face is part of + """ + t1 = _GeomAdaptor_Surface( + _BRep_Tool.Surface(_TopoDS_face(self.shape))).GetType() + return {_GeomAbs.GeomAbs_Plane: 'plane', + _GeomAbs.GeomAbs_Cylinder: 'cylinder', + _GeomAbs.GeomAbs_Cone: 'cone', + _GeomAbs.GeomAbs_Sphere: 'sphere', + _GeomAbs.GeomAbs_Torus: 'torus', + _GeomAbs.GeomAbs_BezierSurface: 'bezier', + _GeomAbs.GeomAbs_BSplineSurface: 'bspline', + _GeomAbs.GeomAbs_SurfaceOfRevolution: 'revolution', + _GeomAbs.GeomAbs_SurfaceOfExtrusion: 'extrusion', + _GeomAbs.GeomAbs_OffsetSurface: 'offset', + _GeomAbs.GeomAbs_OtherSurface: 'other'}[t1] + + +class shell(shape): + """ + A connection of faces. Can be instantiated with a list of faces + to connect. + """ + + stype = 'shell' + + def __init__(self, fs, tolerance=1e-6): + if isinstance(fs, (list, tuple)): + b = _BRepBuilderAPI.BRepBuilderAPI_Sewing(tolerance) + for f in fs: + b.Add(f.shape) + b.Perform() + self.shape = b.SewedShape() + print 'sewing type:', self._raw_type() + elif isinstance(fs, _TopoDS.TopoDS_Shell): + self.shape = fs + elif isinstance(fs, _TopoDS.TopoDS_Shape) and _raw_type(fs) == 'shell': + self.shape = _TopoDS_shell(fs) + else: + raise TypeError + + def center(self): + subs = self.subshapes('face') + c = (0.0, 0.0, 0.0) + total_area = 0.0 + for sub in subs: + sub_center = sub.center() + area = sub.area() + c = (c[0] + area * sub_center[0], + c[1] + area * sub_center[1], + c[2] + area * sub_center[2]) + total_area = total_area + area + c = (c[0] / total_area, c[1] / total_area, c[2] / total_area) + return c + + def area(self): + """ + Returns the area of the shell + """ + subs = self.subshapes('face') + c = (0.0, 0.0, 0.0) + total_area = 0.0 + for sub in subs: + sub_center = sub.center() + area = sub.area() + total_area = total_area + area + return total_area + + +class solid(shape): + """ + A closed and filled shell. Can be instantiated with a list of + shells to connect. + + Currently OCC compund and compsolid are also handled by solid. + That isn't right. Ultimately, should make them their own classes + and type check more carefully ***. + """ + + stype = 'solid' + + def __init__(self, ss): + if isinstance(ss, (list, tuple)): + b = _BRepBuilderAPI.BRepBuilderAPI_MakeSolid() + for s in ss: + b.Add(_TopoDS_shell(s.shape)) + self.shape = b.Solid() + elif isinstance(ss, _TopoDS.TopoDS_Solid): + self.shape = ss + elif isinstance(ss, _TopoDS.TopoDS_Shape): + if _raw_type(ss) == 'solid': + self.shape = _TopoDS.TopoDS_solid(ss) + elif _raw_type(ss) == 'compound': + self.shape = _TopoDS.TopoDS_compound(ss) + elif _raw_type(ss) == 'compsolid': + self.shape = _TopoDS.TopoDS_compsolid(ss) + else: + raise TypeError + else: + raise TypeError + + def __add__(self, other): + return fuse(self, other) + + def __sub__(self, other): + return cut(self, other) + + def __and__(self, other): + return common(self, other) + + def to_stl(self, name, **options): + """ + Exports the solid in .stl format. It supports the following options: + + ascii_mode: + 0: generate a binary stl file + 1 (Default): generate an ascii stl file + + relative_mode: + 0: deflection is calculated according to an absolute number + 1 (Default): deflection is calculated from the relative shape size + + abs_deflection (0.01 Default): for relative_mode 0, deflection is set + to this + + rel_deflection (0.001 Default): for relative_mode 1, deflection is + multiplied by this + + I found blender and gts had trouble with the output. There + were many repeated vertices in ascii or binary mode. Most + could be fixed by importing to blender, removing doubles, and + exporting to stl. *** + """ + + w = _StlAPI_Writer() + if 'ascii_mode' in options: + w.SetASCIIMode(options['ascii_mode']) + if 'relative_mode' in options: + w.SetRelativeMode(options['relative_mode']) + if 'abs_deflection' in options: + w.SetDeflection(options['abs_deflection']) + if 'rel_deflection' in options: + w.SetCoefficient(options['rel_deflection']) + w.Write(self.shape, name) + + def center(self): + g1 = _GProp_GProps() + g2 = _BRepGProp() + g2.VolumeProperties(self.shape, g1) + p = g1.CentreOfMass() + return (p.X(), p.Y(), p.Z()) + + def fillet(self, rad, edge_indices=None): + """ + Fillets the solid at specified edges with specified radii. + + If rad is a float, fillets all edges the same radius. rad may + also be a list of [(rad1, edge_indices1), (rad2, + edge_indices2), ...] where rad1 is the radius to fillet all + edges in edge_indices1, rad2 is the radius to fillet all edges + in edge_indices2, etc. In this latter case, the second + argument (edge_indices) is not used. rad1, rad2, etc. may be + two-tuples instead of floats. In this case, the fillet rad is + an evolutive radius that changes from one radius to another + over the edge. + + If edge_indices is None, fillets all edges. edge_indices may + also be a list of [(x1, y1, z1), (x2, y2, z2), ...] where each + (x1, y1, z1) specifies the edge with center nearest that + point. + + I found OCC's BRepFilletAPI_MakeFillet buggy. Errors could be + improved by the following workarounds: + + 1. Elimate impossible conditions (e.g. a 1x1x1 box with fillet + radius > 0.5) (obviously not an OCC bug) + + 2. Eliminate unneeded edges. OCC's boolean operations often + return two faces in the same domain with an edge between them + that can be merged. Eliminating these edges (by merging the + faces) helped. The simplify() method can do this for some + shapes. It's incomplete, though. Also, seam edges (e.g. the + edge along a cylinder's body) can be moved out of the way + sometimes. + + 3. Change the radius slightly. + + 4. Fillet a few edges, then a few more, then a few more, etc. + edge_center passing can be very useful for this workaround. + All connected fillets should be in the same edge group. + + 5. Extrusion along a spline causes straight edges to create + bspline faces. Planarazing these faces helped. + """ + + raw_edges = self._raw('edge') + if isinstance(rad, (int, float)): + # Make real edge_indices + if edge_indices is None: + edge_indices = range(len(raw_edges)) + if len(edge_indices) <= 0: + return + if not isinstance(edge_indices[0], int): # coordinate positions + edge_indices = self.nearest('edge', edge_indices) + #print 'edge_indices', edge_indices + fillet_rads = [(rad, edge_indices)] + else: + fillet_rads = rad + + b = _BRepFilletAPI.BRepFilletAPI_MakeFillet(self.shape) + changed = 0 + for fillet_rad in fillet_rads: + rad, edge_indices = fillet_rad + if isinstance(rad, (int, float)): + if rad > 0.0: + for edge_index in edge_indices: + changed = 1 + b.Add(rad, _TopoDS_edge(raw_edges[edge_index])) + else: # evolutive radius + for edge_index in edge_indices: + changed = 1 + b.Add(rad[0], rad[1], _TopoDS_edge(raw_edges[edge_index])) + if changed: + self.shape = b.Shape() + + def chamfer(self, dist, edge_indices=None): + """ + chamfers all edges in edge_indices the same distance at 45 degrees + + The arguments are more primitive than boolean--should change + to be like boolean. *** + """ + if dist > 0.0: + edge_map = _TopTools.TopTools_IndexedDataMapOfShapeListOfShape() + _TopExp_MapShapesAndAncestors(self.shape, _TopAbs.TopAbs_EDGE, + _TopAbs.TopAbs_FACE, edge_map) + b = _BRepFilletAPI.BRepFilletAPI_MakeChamfer(self.shape) + raw_edges = self._raw('edge') + if edge_indices is None: + edge_indices = range(len(raw_edges)) + if len(edge_indices) <= 0: + return + if not isinstance(edge_indices[0], int): # coordinate positions + edge_indices = self.nearest('edge', edge_indices) + + for edge_index in edge_indices: + e1 = raw_edges[edge_index] + f1 = edge_map.FindFromKey(e1).First() + b.Add(dist, dist, _TopoDS_edge(e1), _TopoDS_face(f1)) + self.shape = b.Shape() + + def draft(self, angle, pdir, pt, face_indices): + """ + Drafts faces in face_indices by angle from direction pdir and + reference plane that passes through pt. + + I found OCC's BRepOffsetAPI_DraftAngle buggy ***. In most + cases, it was better to hand-create two wire profiles, and use + a loft with ruled edges. + """ + if angle != 0.0: + d = _gp.gp_Dir(pdir[0], pdir[1], pdir[2]) + pln = _gp.gp_Pln(_gp.gp_Pnt(pt[0], pt[1], pt[2]), d) + raw_faces = self._raw('face') + if not isinstance(face_indices[0], int): # coordinate positions + face_indices = self.nearest('face', face_indices) + b = _BRepOffsetAPI.BRepOffsetAPI_DraftAngle(self.shape) + for face_index in face_indices: + b.Add(_TopoDS_face(raw_faces[face_index]), d, angle, pln) + b.Build() + self.shape = b.Shape() + + def volume(self): + """ + Returns the volume of the solid + """ + g1 = _GProp_GProps() + g2 = _BRepGProp() + g2.VolumeProperties(self.shape, g1) + return g1.Mass() # Returns volume when density hasn't been set + + def simplify(self, skip_edges=0, skip_faces=0, skip_fits=0, stopat=-1, tolerance=1e-3): + """ + Fuses edges that are C1 continuous and share a vertex. Fuses + faces in the same domain that share an edge. It's currently + slow, because FacesIntersector is slow. (It's not the python + code.) May want to remove internal edges and internal vertices + later. *** + """ + + """ + # Seemed simple, but didn't work. Glancing through the + # C-code, I don't think BOP_Refiner is a fusing algorithm. + b = BOP_Refiner() + b.SetShape(self.shape) + b.Do() + print 'Removed', b.NbRemovedVertices(), 'vertices' + print 'Removed', b.NbRemovedEdges(), 'edges' + self.shape = b.Shape() + """ + + """ + # Seemed simple, but didn't work + b1 = BlockFix_BlockFixAPI() + b1.SetShape(self.shape) + b1.SetTolerance(tolerance) + b1.Perform() + self.shape = b1.Shape() + """ + + # Fuse Edges first + if not skip_edges: + b = _TopOpeBRepTool_FuseEdges(self.shape) + self.shape = b.Shape() + + # Fuse Faces second (not easy) + if not skip_faces: + edge_map = _TopTools.TopTools_IndexedDataMapOfShapeListOfShape() + _TopExp_MapShapesAndAncestors(self.shape, _TopAbs.TopAbs_EDGE, + _TopAbs.TopAbs_FACE, edge_map) + raw_edges = self._raw('edge') + common_faces = {} + new_faces = [] + pairs = [] + merge_count = 0 + for rec, raw_edge in enumerate(raw_edges): + if stopat >= 0 and rec == stopat: + break + print 'raw edge', rec, + l1 = edge_map.FindFromKey(raw_edge) + f1 = l1.First() # Assumes only two faces per edge + f2 = l1.Last() + h1 = f1.__hash__() + h2 = f2.__hash__() + if h1 == h2: # Avoid seam edges + print 'Seam' + continue + elif h1 > h2: + pair = (h2, h1) + else: + pair = (h1, h2) + if pair in pairs: + print 'Skipped' + continue + else: + pairs.append(pair) + if _raw_faces_same_domain(f1, f2, skip_fits): + print 'Merge' + merge_count = merge_count + 1 + if f1 not in common_faces: + if f2 not in common_faces: + new_faces.append(_raw_faces_merge(f1, f2)) + index = len(new_faces) - 1 + common_faces[f1] = index + common_faces[f2] = index + else: + index = common_faces[f2] + # Changed to sewed faces to handle recursive edge + # changes from ShapeFix + #to_merge = (new_faces[index], f1) + b = _BRepBuilderAPI.BRepBuilderAPI_Sewing() + b.Add(new_faces[index]) + b.Add(f1) + b.Perform() + s = shell(b.SewedShape()) + nf1, nf2 = s._raw('face') + new_faces[index] = _raw_faces_merge(nf1, nf2) + common_faces[f1] = index + elif f2 not in common_faces: + index = common_faces[f1] + # Changed to sewed faces to handle recursive edge + # changes from ShapeFix + #to_merge = (new_faces[index], f2) + b = _BRepBuilderAPI.BRepBuilderAPI_Sewing() + b.Add(new_faces[index]) + b.Add(f2) + b.Perform() + s = shell(b.SewedShape()) + nf1, nf2 = s._raw('face') + new_faces[index] = _raw_faces_merge(nf1, nf2) + common_faces[f2] = index + else: # Both in common_faces + if common_faces[f1] == common_faces[f2]: + print 'Done already' + else: + index = common_faces[f1] + index2 = common_faces[f2] + b = _BRepBuilderAPI.BRepBuilderAPI_Sewing() + b.Add(new_faces[index]) + b.Add(new_faces[index2]) + b.Perform() + s = shell(b.SewedShape()) + nf1, nf2 = s._raw('face') + new_faces[index] = _raw_faces_merge(nf1, nf2) + for k, v in common_faces.items(): + if v == index2: + common_faces[k] = index + new_faces[index2] = None + + else: + print 'Different' + + if len(new_faces) <= 0: # No common faces + return + else: + print + # BRep_Builder may be faster than BRepBuilderAPI_Sewing + raw_faces = self._raw('face') + for f in common_faces.keys(): + raw_faces.remove(f) + b = _BRepBuilderAPI.BRepBuilderAPI_Sewing(tolerance) + for f in raw_faces: + b.Add(f) + for f in new_faces: + if f: + b.Add(f) + b.Perform() + new_shell = b.SewedShape() + if _raw_type(new_shell) == 'shell': + b2 = _BRepBuilderAPI.BRepBuilderAPI_MakeSolid() + b2.Add(_TopoDS_shell(new_shell)) + self.shape = b2.Solid() + elif _raw_type(new_shell) == 'compound': + print 'Warning: simplify() returned compound' + s = solid(new_shell) + css = s._raw('shell') + c = _TopoDS.TopoDS_Compound() + b3 = _BRep_Builder() + b3.MakeCompound(c) + for cs in css: + b2 = _BRepBuilderAPI.BRepBuilderAPI_MakeSolid() + b2.Add(_TopoDS_shell(cs)) + b3.Add(c, b2.Solid()) + self.shape = c + else: + print 'Warning: Wrong Sewed Shape after simplify():', _raw_type(new_shell) + self.shape = new_shell + + +""" +Primitives +---------- + +Philosophy +---------- +OCC offers a variety of primitive input arguments. Users typically +use 1-2 of them, and the others cause confusion for those who don't +use them. Instead, only offer the variety that provides unique +topologies. Those varieties with differing positions and orientations +are not used. They can be arrived at with transformations. +""" + + +# Edge Primitives + +def segment(pt1, pt2): + """ + Returns an edge that is a segment from point1 to point2. + Expects point1, point2 + """ + return edge(_BRepBuilderAPI.BRepBuilderAPI_MakeEdge(_gp.gp_Pnt(pt1[0], pt1[1], pt1[2]), _gp.gp_Pnt(pt2[0], pt2[1], pt2[2])).Edge()) + + +def arc(rad, start_angle, end_angle): + """ + Returns an edge that is an arc centered about (0.0, 0.0, 0.0) with + given radius, start_angle, and end_angle. + Expects radius, start_angle, end_angle + """ + return edge(_BRepBuilderAPI.BRepBuilderAPI_MakeEdge(_GC_MakeArcOfCircle(_gp.gp_Circ(_gp.gp_Ax2(_gp.gp_Pnt(0.0, 0.0, 0.0), _gp.gp_Dir(0.0, 0.0, 1.0)), rad), start_angle, end_angle, 0).Value()).Edge()) + + +def arc_ellipse(rad1, rad2, start_angle, end_angle): + """ + Returns an edge that is an elliptical arc centered about (0.0, + 0.0, 0.0) with given major radius rad1, minor radius rad2, + start_angle, and end_angle. Expects rad1, rad2, start_angle, + end_angle. + """ + if rad2 > rad1: + print 'Error: Major radius ', rad1, ' must be greater than minor radius ', rad2 + _sys.exit() + return edge(_BRepBuilderAPI.BRepBuilderAPI_MakeEdge(_GC_MakeArcOfEllipse(_gp.gp_Elips(_gp.gp_Ax2(_gp.gp_Pnt(0.0, 0.0, 0.0), _gp.gp_Dir(0.0, 0.0, 1.0)), rad1, rad2), start_angle, end_angle, 0).Value()).Edge()) + + +def spline(pts, **options): + """ + Returns an edge that is a 3D spline curve fitted through the + passed points. + Expects a list of points. + + Note: the name really ought to be fit_spline, or something like + that to later allow someone to actually enter knots and such. + Change if need be. + """ + if not 'min_degree' in options: + options['min_degree'] = 3 + if not 'max_degree' in options: + options['max_degree'] = 8 + if not 'continuity' in options: + options['continuity'] = _GeomAbs.GeomAbs_C2 + if not 'tolerance' in options: + options['tolerance'] = 1e-3 + + num_pts = len(pts) + tpts = _TColgp_Array1OfPnt(0, num_pts-1) + for count in range(num_pts): + tpts.SetValue(count, _gp.gp_Pnt(pts[count][0], pts[count][1], pts[count][2])) + return edge(_BRepBuilderAPI.BRepBuilderAPI_MakeEdge(_GeomAPI_PointsToBSpline(tpts, options['min_degree'], options['max_degree'], options['continuity'], options['tolerance']).Curve()).Edge()) + + +def bezier(pts, weights=[]): + """ + Returns an edge that is a Bezier curve fitted through pts. Only + the first and last points does it pass through. The others are + control points. weights is a list pts long. If unspecified, all + points have the same weight. + """ + num_pts = len(pts) + tpts = _TColgp_Array1OfPnt(0, num_pts-1) + for count in range(num_pts): + tpts.SetValue(count, _gp.gp_Pnt(pts[count][0], + pts[count][1], + pts[count][2])) + if len(weights) == num_pts: + tweights = _TColStd_Array1OfReal(1, num_pts) + for count in range(num_pts): + tweights.SetValue(count + 1, weights[count]) + retval = edge(_BRepBuilderAPI.BRepBuilderAPI_MakeEdge(_Geom_BezierCurve(tpts, tweights).GetHandle()).Edge()) + else: + retval = edge(_BRepBuilderAPI.BRepBuilderAPI_MakeEdge(_Geom_BezierCurve(tpts).GetHandle()).Edge()) + + return retval + + +def circle(rad): + """ + Returns an edge that is a circle centered at (0.0, 0.0, 0.0) with + given radius. Expects a radius + """ + return edge(_BRepBuilderAPI.BRepBuilderAPI_MakeEdge(_gp.gp_Circ(_gp.gp_Ax2(_gp.gp_Pnt(0.0, 0.0, 0.0), _gp.gp_Dir(0.0, 0.0, 1.0)), rad)).Edge()) + + +def ellipse(rad1, rad2): + """ + Returns an edge that is an ellipse centered at (0.0, 0.0, 0.0) + with major radius rad1 and minor radius rad2. + """ + if rad2 > rad1: + print 'Error: Major radius ', rad1, ' must be greater than minor radius ', rad2 + _sys.exit() + return edge(_BRepBuilderAPI.BRepBuilderAPI_MakeEdge(_gp.gp_Elips(_gp.gp_Ax2(_gp.gp_Pnt(0.0, 0.0, 0.0), _gp.gp_Dir(0.0, 0.0, 1.0)), rad1, rad2)).Edge()) + + +# Wire Primitives + +def polygon(pts): + """ + Returns a wire which is a polygon. + Expects a list of points + """ + + b = _BRepBuilderAPI.BRepBuilderAPI_MakePolygon() + for count in range(len(pts)): + b.Add(_gp.gp_Pnt(pts[count][0], pts[count][1], pts[count][2])) + return wire(b.Wire()) + + +def rectangle(dx, dy): + """ + Returns a wire in the shape of a rectangle. The lower left + coordinate is (0,0). + """ + return polygon([(0.0, 0.0, 0.0), + (dx, 0.0, 0.0), + (dx, dy, 0.0), + (0.0, dy, 0.0), + (0.0, 0.0, 0.0)]) + + +def ngon(rad, n): + """ + Returns a wire which is an ngon (e.g. 6gon is a hexagon) in the x-y + plane. Expects a radius and a number of sides + """ + + angle = 0.0 + pts = [] + for count in range(n+1): + angle = angle + 2*_math.pi/n + pts.append((rad*_math.cos(angle), rad*_math.sin(angle), 0.0)) + return polygon(pts) + + +def helix(rad, angle, turns, eps=1e-12): + """ + Returns a wire that is a helix centered at (0.0, 0.0, 0.0) with + given radius, helix angle, and number of turns. + + turns currently must be an integer multiple of 0.25. + + This used to return an edge. I found a way for an exact edge, but + the underlying curve was a spline, and it degenerated over many + turns. I replaced it with a wire that is a spline over quarter + turns and replicated. + """ + + # This routine made a nice edge, but the underlying curve was a + # spline fit that had trouble with many revolutions. + """ + # Define the parametric line + curve = GCE2d_MakeSegment(gp_Pnt2d(0.0, 0.0), gp_Pnt2d(2*_math.pi*turns, 2*_math.pi*rad*turns*_math.tan(angle))) + # Define the surface to wrap the line on + surface = Geom_CylindricalSurface(gp_Ax3(gp_Ax2(gp_Pnt(0.0, 0.0, 0.0), gp_Dir(0.0, 0.0, 1.0))), rad) + raw_edge = BRepBuilderAPI_MakeEdge(curve.Operator(), surface.GetHandle()).Edge() + BRepLib.BuildCurve3d(raw_edge) # edge orientation messed up with this--seems to use a spline + return edge(raw_edge) + """ + + # This routine breaks the helix into quarter circles of beziers. + # It is exact, since a properly weighted bezier generates a + # circle. + + # fits and returns a wire. + full_angle = _math.pi/2 + frac_parts = 4 * turns # Change if full_angle changes + num_parts = int(frac_parts) + rem_parts = frac_parts - num_parts + if abs(rem_parts) > eps: + print 'Error: Fractional turns not currently supported.' + _sys.exit() + + # Calculate a quarter helix using a weighted bezier + z0 = rad * full_angle * _math.tan(angle) + e1 = bezier([(rad, 0.0, 0.0), (rad, rad, z0/2), (0.0, rad, z0)], [1.0, 1.0/_math.sqrt(2.0), 1.0]) + + # Replicate the edge, spinning and translating, to make a helix + retval = [] + for count in range(num_parts): + locale = e1.copy() + locale.rotatez(count * full_angle) + locale.translate((0.0, 0.0, count * z0)) + retval.append(locale) + return wire(retval) + + +# Face Primitives + +def plane(w1, inner_wires=[]): + """ + Returns a face which is a plane. + + w1 is a wire that bounds the outside of the face + inner_wires are a list of wires that are holes in the face + + Expects all wires are planarized. + """ + # w1 must be planar! + ow1 = _TopoDS_wire(w1.shape) + #ow1.Orientation(TopAbs_EXTERNAL) # This made them disappear + b = _BRepBuilderAPI.BRepBuilderAPI_MakeFace(ow1) + for inner_wire in inner_wires: + iw = _TopoDS_wire(inner_wire.shape) + #iw.Orientation(TopAbs_EXTERNAL) # This didn't help + b.Add(iw) + if not b.IsDone(): + raise NameError('FaceError') + else: + retval = face(b.Face()) + # This is a poor way to fix an orientation problem *** + if len(inner_wires) > 0: + retval.fix() + return retval + + +def face_from(f1, w1): + """ + Returns a face whose underlying geometry is the same underlying + geometry of f1 but is bounded by the closed wire w1. + + Trim is a misnomer. w1 can be beyond the original face. + """ + s = _BRep_Tool.Surface(_TopoDS_face(f1.shape)) + b = _BRepBuilderAPI.BRepBuilderAPI_MakeFace(s, _TopoDS_wire(w1.shape)) + return face(b.Face()) + + +def filling(w1, **options): + """ + Returns a face which is a 3D surface fit to the wire w1. Warning: + OCC may modify the wire slightly to fit the surface. Expects a + closed curved wire + """ + call_options = {} + if 'degree' in options: + call_options['Degree'] = options['degree'] + if 'max_degree' in options: + call_options['MaxDeg'] = options['max_degree'] + if 'continuity' in options: + occ_cont = [_GeomAbs.GeomAbs_C0, _GeomAbs.GeomAbs_C1, + _GeomAbs.GeomAbs_C2][continuity] + else: + occ_cont = _GeomAbs.GeomAbs_C0 + if 'num_pts' in options: + call_options['NbPtsOnCur'] = options['num_pts'] + if 'num_iters'in options: + call_options['NbIter'] = options['num_iters'] + if 'anisotropy' in options: + call_options['Anisotropie'] = options['anisotropy'] + if 'tolerance2d'in options: + call_options['Tol2d'] = options['tolerance2d'] + if 'tolerance3d' in options: + call_options['Tol3d'] = options['tolerance3d'] + if 'tolerance_angle' in options: + call_options['TolAng'] = options['tolerance_angle'] + if 'tolerance_curve' in options: + call_options['TolCurv'] = options['tolerance_curve'] + if 'max_segments' in options: + call_options['MaxSegments'] = options['max_segments'] + + raw_edges = w1._raw('edge') + b = _BRepOffsetAPI.BRepOffsetAPI_MakeFilling(**call_options) + for raw_edge in raw_edges: + b.Add(_TopoDS_edge(raw_edge), occ_cont) + b.Build() + return face(b.Shape()) + + +def slice(s1, x=None, y=None, z=None): + """ + Returns a slice of solid s1. A slice is a list of faces derived + from a plane cutting through s1. + + x can be the plane. It's up to you to make sure the plane extends + beyond s1's bounds. If not, only specify one of x, y, or z as a + float. For example, x = 5.0 specifies the plane with any value of + y or z that always has x = 5.0. + """ + if isinstance(x, face): + p1 = x + else: + xmin, ymin, zmin, xmax, ymax, zmax = s1.bounds() + if x: + w1 = polygon([(x, ymin, zmin), + (x, ymax, zmin), + (x, ymax, zmax), + (x, ymin, zmax), + (x, ymin, zmin)]) + elif y: + w1 = polygon([(xmin, y, zmin), + (xmax, y, zmin), + (xmax, y, zmax), + (xmin, y, zmax), + (xmin, y, zmin)]) + else: + w1 = polygon([(xmin, ymin, z), + (xmax, ymin, z), + (xmax, ymax, z), + (xmin, ymax, z), + (xmin, ymin, z)]) + p1 = plane(w1) + + b1 = _BRepAlgoAPI.BRepAlgoAPI_Common(s1.shape, p1.shape) + s2 = solid(b1.Shape()) + return s2.subshapes('face') + + +# Solid Primitives + +def box(dx, dy, dz): + """ + Returns a solid box. + + The box fills the x-direction from 0 to dx, + the y-direction from 0 to dy, + and the z-direction from 0 to dz. + """ + return solid(_BRepPrimAPI.BRepPrimAPI_MakeBox(dx, dy, dz).Shape()) + + +def wedge(dx, dy, dz, lx, xmax=None, zmin=None, zmax=None): + """ + Returns a solid wedge. + Expects dx, dy, dz, lx -or- dx, dy, dz, xmin, xmax, zmin, zmax + + dx, dy, and dz follow the box conventions. + + If only lx is given, dx defines the xlength at y=0 and lx defines + the xlength at y=dy. + + If xmin, xmax, zmin, and zmax are given, xmin and xmax define the + x wedge limits at y=dy, and zmin and zmax define the z wedge + limits at y=dy. + + The limits at y=0 follow the box convention. + """ + + if xmax is None and zmin is None and zmax is None: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeWedge(dx, dy, dz, lx).Shape()) + else: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeWedge(dx, dy, dz, lx, xmax, zmin, zmax).Shape()) + + +def cylinder(rad, height, angle=None): + """ + Returns a solid cylinder. + + If angle is given, it limits the cylinder in the x-y plane. + """ + + if angle is None: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeCylinder(rad, height).Shape()) + else: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeCylinder(rad, height, angle).Shape()) + + +def sphere(rad, angle1=None, angle2=None, angle3=None): + """ + Returns a solid sphere. + + rad is the sphere radius. + If only angle1 is given, angle1 limits the sphere in the x-y plane. + If only angle1 and angle2 are given, they limit the sphere to a + lower latitude (angle1) and an upper latitude (angle2). + If all three angles are given, angle1 limits the x-y plane, and + angle2 and angle3 limit the latitudes. + """ + + if angle1 is None and angle2 is None and angle3 is None: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeSphere(rad).Shape()) + elif angle2 is None and angle3 is None: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeSphere(rad, angle1).Shape()) + elif angle3 is None: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeSphere(rad, angle1, angle2).Shape()) + else: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeSphere(rad, angle1, angle2, angle3).Shape()) + + +def cone(rad1, rad2, height, angle=None): + """ + Returns a solid cone. + + rad1 is the radius at z=0. + rad2 is the radius at z=height. + height is the cone height. + """ + if angle is None: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeCone(rad1, rad2, height).Shape()) + else: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeCone(rad1, rad2, height, angle).Shape()) + + +def bezier_cone(rad1, rad2, height, angle=None): + """ + Returns a solid cone where the faces are plane or splines. + + I found cone pretty buggy when fillets were needed. This should + yield an identical solid, but it sometimes was nicer to fillets. + + I think this would be even more robust if I knew the proper + coefficients to make a bezier a cone instead of revolving it. + Surfaces of revolution have their own problems. + """ + if angle is None: + angle = 2.0 * _math.pi + e1 = bezier([(rad1, 0.0, 0.0), (rad2, 0.0, height)]) + w1 = polygon([(rad2, 0.0, height), + (0.0, 0.0, height), + (0.0, 0.0, 0.0), + (rad1, 0.0, 0.0)]) + w1 = wire([e1, w1]) + return revol(plane(w1), (0.0, 0.0, 0.0), (0.0, 0.0, 1.0), angle) + + +def torus(rad1, rad2, angle1=None, angle2=None, angle3=None): + """ + Returns a solid torus. + + rad1 is the distance from torus center to extruded circle center. + rad2 is the extruded circle radius. + If only angle1 is given, angle1 limits the extrusion to angle1 + radians, instead of 2*pi radians. + If only angle1 and angle2 are given, they limit the torus to a + lower latitude (angle1) and an upper latitude (angle2). + If all three angles are given, angle1 limits the extrusion, and + angle2 and angle3 limit the latitudes. + """ + if angle1 is None and angle2 is None and angle3 is None: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeTorus(rad1, rad2).Shape()) + elif angle2 is None and angle3 is None: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeTorus(rad1, rad2, angle1).Shape()) + elif angle3 is None: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeTorus(rad1, rad2, angle1, angle2).Shape()) + else: + return solid(_BRepPrimAPI.BRepPrimAPI_MakeTorus(rad1, rad2, angle1, angle2, angle3).Shape()) + + +def prism(s, pdir): + """ + Returns a solid which is an extrusion of a face along a direction, + or a shell which is an extrusion of a wire, + or a face which is an extrusion of an edge, + or an edge which is an extrusion of a vertex. + + Expects a shape to be extruded and a prism direction (dx, dy, dz). + Currently ignores shell to composite solid possibilities. + """ + b = _BRepPrimAPI.BRepPrimAPI_MakePrism(s.shape, _gp.gp_Vec(pdir[0], pdir[1], pdir[2]), True) + b.Build() + if s.stype == 'vertex': + return edge(b.Shape()) + elif s.stype == 'edge': + return face(b.Shape()) + elif s.stype == 'wire': + return shell(b.Shape()) + elif s.stype == 'face': + return solid(b.Shape()) + else: + print 'Error: Improper type for prism', s.stype + + +def revol(s, pabout, pdir, angle): + """ + Returns a solid which is a revolution of a face, + or a shell which is a revolution of a wire, + or a face which is a revolution of an edge, + or an edge which is a revolution of a vertex. + + Expects a shape to be revolved, an about point, an about + direction, and the angle to revolve the shape. + """ + b = _BRepPrimAPI.BRepPrimAPI_MakeRevol(s.shape, _gp.gp_Ax1(_gp.gp_Pnt(pabout[0], pabout[1], pabout[2]), _gp.gp_Dir(pdir[0], pdir[1], pdir[2])), angle, 1) + b.Build() + if s.stype == 'vertex': + return edge(b.Shape()) + elif s.stype == 'edge': + return face(b.Shape()) + elif s.stype == 'wire': + return shell(b.Shape()) + elif s.stype == 'face': + return solid(b.Shape()) + else: + print 'Error: Improper type for prism', s.stype + + +def loft(ws, ruled=False, stype='solid'): + """ + Returns a solid or shell which is a fit of a list of closed wires. + Expects a list of closed wires. + + I found OCC's BRepOffsetAPI_ThruSections buggy when each wire + profile wasn't planar. + """ + + if stype == 'shell': + b = _BRepOffsetAPI.BRepOffsetAPI_ThruSections(False, ruled) + else: + b = _BRepOffsetAPI.BRepOffsetAPI_ThruSections(True, ruled) + for w in ws: + b.AddWire(_TopoDS_wire(w.shape)) + b.Build() + if stype == 'shell': + return shell(b.Shape()) + else: + return solid(b.Shape()) + + +def plane_loft(ws, stype='solid'): + """ + Returns a solid or shell which is a fit of a list of closed wires. + Expects a list of closed wires. + + It assumes the generating wires fit in a plane, and it assumes + each connection face is a plane. All wires should have the same + number of points. + + Currently, loft() often returns bsplines when the face should be + planar. This forces planar faces for the rare occasions when a + loft should only have planar faces. It would be better to find a + routine that simplifies bsplines into primitives when possible + ***. + """ + + profiles = [] + for w in ws: + vs = w.subshapes('vertex') + profile = [] + for v in vs: + profile.append(v.center()) + profile.append(profile[0]) + profiles.append(profile) + faces = [] + for pt_index in range(len(profiles[0])-1): + for profile_index in range(len(profiles)-1): + p = polygon([ + profiles[profile_index][pt_index], + profiles[profile_index][pt_index+1], + profiles[profile_index+1][pt_index+1], + profiles[profile_index+1][pt_index], + profiles[profile_index][pt_index]]) + try: + faces.append(plane(p)) + except NameError: + print 'Error: Not Planar' + _sys.exit() + # The loft must have slightly changed edges or vertices, + # because this was a mess. + #w1 = polygon([profiles[profile_index][pt_index], + # profiles[profile_index][pt_index+1]]) + #w2 = polygon([profiles[profile_index+1][pt_index], + # profiles[profile_index+1][pt_index+1]]) + #faces.append(loft([w1, w2], 1)) + + if stype == 'solid': + faces.append(plane(polygon(profiles[0]))) + faces.append(plane(polygon(profiles[-1]))) + s = shell(faces) + s.fix() + if stype == 'solid': + return solid([s]) + else: + return s + + +def pipe(profile, spine, continuous=False, transition='sharp', stype='solid', **options): + """ + Returns a solid which is an extrusion of a closed wire profile + along a wire spine. Extrusion at discontinuities is controlled + with the transition option. + Expects a profile and a spine. + + For discontinuous spines, profile may be a list of profiles to be + evaluated along the spine. This forces a certain normal to the + spine, which can be helpful for spines which don't sit in a plane. + + If the spine is continuous, change continuous to 1 to avoid bugs. + + Make sure the profile sits on the spine and is oriented + perpendicular to the spine's direction. When you don't do this, I + didn't understand the returned value. + """ + + if continuous: + if stype == 'shell': + b = _BRepOffsetAPI.BRepOffsetAPI_MakePipe(_TopoDS_wire(spine.shape), profile.shape) + b.Build() + return shell(b.Shape()) + else: + b = _BRepOffsetAPI.BRepOffsetAPI_MakePipe(_TopoDS_wire(spine.shape), plane(profile).shape) # Only works with planar profile *** + b.Build() + return solid(b.Shape()) + + else: + raw_modes = {'round': _BRepBuilderAPI.BRepBuilderAPI_RoundCorner, + 'sharp': _BRepBuilderAPI.BRepBuilderAPI_RightCorner, + 'transform': _BRepBuilderAPI.BRepBuilderAPI_Transformed} + b = _BRepOffsetAPI.BRepOffsetAPI_MakePipeShell(_TopoDS_wire(spine.shape)) + #b.SetTolerance(1e-4, 1e-4, 1e-2) # Default + #b.SetTolerance(1e-6, 1e-6, 1e-4) # Didn't help + if 'contact' in options: + contact = options['contact'] + else: + contact = 0 + if 'correct' in options: + correct = options['correct'] + else: + correct = 0 + if isinstance(profile, list): + for p in profile: + b.Add(p.shape, contact, correct) + else: + b.Add(profile.shape, contact, correct) + b.SetTransitionMode(raw_modes[transition]) + b.Build() + if stype == 'shell': + return shell(b.Shape()) + else: + b.MakeSolid() + return solid(b.Shape()) + + +def helical_solid(profile, rad, angle, turns): + """ + Returns a profile spun in a helix. Why not use pipe? Pipe + changed the orientation of the profile. This routine fixes the + orientation correctly. + + profile is in the xy plane. The helix will pass through (0,0). + + Turns must be an integer multiple of 0.25 + + This routine only generates a quarter helix solid, then replicates + and boolean merges to make the full solid. Therefore, it is very + expensive. There must be a better way. I am also uncertain how + interpolation is done along the spine. It may not be exact. + """ + + # Create a quarter of the solid + local_turns = 0.25 + spine = helix(rad, angle, local_turns) + # Orient the profile normal to the helix + profile1 = profile.copy() + #profile1.rotatex(_math.pi/2 + angle) # This made you have to + #cut everything + profile1.scaley(1.0 / _math.cos(angle)) + profile1.rotatex(_math.pi/2) + profile1.translate((rad, 0.0, 0.0)) + profiles = [] + for count in range(2): + local_profile = profile1.copy() + local_profile.rotatez(count * _math.pi/2) + local_profile.translate((0.0, 0.0, count * rad * _math.pi/2 * _math.tan(angle))) + profiles.append(local_profile) + quarter_thread = pipe(profiles, spine, 0) + + # Spin and translate the quarter into the full + retval = quarter_thread.copy() + for count in range(1, int(round(turns * 4))): + local_thread = quarter_thread.copy() + local_thread.rotatez((count % 4) * _math.pi/2) + local_thread.translate((0.0, 0.0, count * rad * _math.pi/2 * _math.tan(angle))) + retval = retval + local_thread + return retval + + +# Useful functions that return arbitrary shapes + +def offset(s1, dist, tolerance=1e-3, join='arc'): + """ + Returns a list of solids which compose the offset from a solid + or a list of faces which compose the offset from a face + + Both BRepOffsetAPI_MakeOffsetShape and BRepOffsetAPI_MakeOffset + fail under way too many normal conditions *** + """ + j = {'arc': _GeomAbs.GeomAbs_Arc, + 'tan': _GeomAbs.GeomAbs_Tangent, + 'int': _GeomAbs.GeomAbs_Intersection}[join] + + if s1.stype == 'solid': + b = _BRepOffsetAPI.BRepOffsetAPI_MakeOffsetShape( + s1.shape, dist, tolerance, False, False, j) + raw_shape = b.Shape() + ss = [] + if _raw_type(raw_shape) == 'compound': + ex = _TopExp_Explorer(raw_shape, _TopAbs.TopAbs_SOLID) + while ex.More(): + ss.append(solid(ex.Current())) + ex.Next() + elif _raw_type(raw_shape) == 'solid': + ss.append(solid(raw_shape)) + else: + print 'Warning: Unexpected type', _raw_type(raw_shape) + return ss + + elif s1.stype == 'face': + s1.shape.Orientation(_TopAbs.TopAbs_FORWARD) + rawf = _TopoDS_face(s1.shape) + brt = _BRep_Tool() + surf = brt.Surface(rawf) + b = _BRepOffsetAPI.BRepOffsetAPI_MakeOffset(rawf, j) + b.Perform(dist) + raw_shape = b.Shape() + + # This worked too + #b = BRepFill_OffsetWire(rawf, j) + #b.Perform(dist) + #s = b.Shape() + + fs = [] + if _raw_type(raw_shape) == 'compound': # Resulting wires broken + ex = _TopExp_Explorer(raw_shape, _TopAbs.TopAbs_WIRE) + while ex.More(): + bf = _BRepBuilderAPI.BRepBuilderAPI_MakeFace(surf, _TopoDS_wire(ex.Current())) + fs.append(face(bf.Face())) + ex.Next() + elif raw_shape.ShapeType() == _TopAbs.TopAbs_EDGE: # Over-simplified + bw = _BRepBuilderAPI.BRepBuilderAPI_MakeWire() + bw.Add(raw_shape) + bf = _BRepBuilderAPI.BRepBuilderAPI_MakeFace(surf, _TopoDS_wire(bw.Wire())) + fs.append(face(bf.Face())) + elif raw_shape.ShapeType() == _TopAbs.TopAbs_WIRE: + bf = _BRepBuilderAPI.BRepBuilderAPI_MakeFace(surf, _TopoDS_wire(raw_shape)) + fs.append(face(bf.Face())) + return fs + + else: + print 'Error: Only solid or face allowed for offset' diff --git a/src/contrib/ccad/setup.py b/src/contrib/ccad/setup.py new file mode 100644 index 000000000..54f011264 --- /dev/null +++ b/src/contrib/ccad/setup.py @@ -0,0 +1,51 @@ +""" +Description +----------- +distutils setup.py for ccad. View model.py for a full description of +ccad. + +Author +------ +Charles Sharman + +License +------- +Distributed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +View LICENSE for details. + +Revision History +---------------- +1/11/14: Began +""" + +import os, sys + +import distutils.core +import distutils.dir_util +import distutils.sysconfig + +name = 'ccad' + +from display import version + +# Install the module +distutils.core.setup(name = name, + version = version, + url = 'UNKNOWN', + py_modules = ['ccad.model', 'ccad.display'], + package_dir = {'ccad': '.'}, + requires = ['occe', 'pythonocc', 'pygtk', 'gtkglext'] + ) + +# Install the documentation +# This is probably not the *proper* way to do this. *** +dist = distutils.core._setup_distribution +if 'install' in dist.commands: + if dist.command_options['install'].has_key('prefix'): + prefix = dist.command_options['install']['prefix'][1] + elif dist.command_options['install'].has_key('home'): + prefix = dist.command_options['install']['home'][1] + else: + prefix = distutils.PREFIX + if prefix: + distutils.dir_util.copy_tree('doc/html', os.path.join(prefix, 'share/doc/ccad/doc/html')) # Hard-coding share into this makes it a linux-only distribution *** diff --git a/src/contrib/ccad/unittest/model_unittest.py b/src/contrib/ccad/unittest/model_unittest.py new file mode 100644 index 000000000..2d92cbe3e --- /dev/null +++ b/src/contrib/ccad/unittest/model_unittest.py @@ -0,0 +1,1215 @@ +""" +Description +----------- +ccad unittest for model.py. View ../model.py for a full description +of ccad. + +Test suite progresses exactly in the same order as model.py code. +Comments that separate blocks of model code are included here. + +Author +------ +Charles Sharman + +License +------- +Distributed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +View LICENSE for details. + +Notes +----- +1. Make every Test class begin with the word Test to make suite work +2. Basic functionality caught in most places, particularly on + different arguments. Add more stringency as needed. +""" + +import ccad + +import math +import unittest +import sys + +def dp(p1, p2): + retval = [] + for count in range(len(p1)): + retval.append(p1[count] - p2[count]) + return retval + +def close(p1, p2, eps = 1e-6): + """ + Makes sure pt1 and pt2 overlap within eps + """ + if isinstance(p1, float) and isinstance(p2, float): + return abs(p1 - p2) < eps + else: # sequence assumed + if len(p1) != len(p2): + return False + retval = True + for count in range(len(p1)): + if abs(p1[count] - p2[count]) >= eps: + retval = False + break + return retval + +# Shape Functions +class TestShapeFunctions(unittest.TestCase): + + def test_translated(self): + delta = (0.1, 0.2, 0.3) + s1 = ccad.sphere(1.0) + s2 = ccad.translated(s1, delta) + self.assert_(close(s2.center(), delta)) + + def test_rotated(self): + s1 = ccad.sphere(1.0) + s2 = ccad.rotated(s1, (1.0, 0.0, 0.0), (0.0, 0.0, 1.0), math.pi/2) + self.assert_(close(s2.center(), (1.0, -1.0, 0.0))) + + def test_rotatedx(self): + s1 = ccad.sphere(1.0) + s1.translate((0.0, 0.0, 1.0)) + s2 = ccad.rotatedx(s1, math.pi/2) + self.assert_(close(s2.center(), (0.0, -1.0, 0.0))) + + def test_rotatedy(self): + s1 = ccad.sphere(1.0) + s1.translate((1.0, 0.0, 0.0)) + s2 = ccad.rotatedy(s1, math.pi/2) + self.assert_(close(s2.center(), (0.0, 0.0, -1.0))) + + def test_rotatedz(self): + s1 = ccad.sphere(1.0) + s1.translate((1.0, 0.0, 0.0)) + s2 = ccad.rotatedz(s1, math.pi/2) + self.assert_(close(s2.center(), (0.0, 1.0, 0.0))) + + def test_mirrored(self): + s1 = ccad.sphere(1.0) + s2 = ccad.mirrored(s1, (1.0, 0.0, 0.0), (1.0, 0.0, 0.0)) + self.assert_(close(s2.center(), (2.0, 0.0, 0.0))) + + def test_mirroredx(self): + s1 = ccad.sphere(1.0) + s1.translate((1.0, 0.0, 0.0)) + s2 = ccad.mirroredx(s1) + self.assert_(close(s2.center(), (-1.0, 0.0, 0.0))) + + def test_mirroredy(self): + s1 = ccad.sphere(1.0) + s1.translate((0.0, 1.0, 0.0)) + s2 = ccad.mirroredy(s1) + self.assert_(close(s2.center(), (0.0, -1.0, 0.0))) + + def test_mirroredz(self): + s1 = ccad.sphere(1.0) + s1.translate((0.0, 0.0, 1.0)) + s2 = ccad.mirroredz(s1) + self.assert_(close(s2.center(), (0.0, 0.0, -1.0))) + + def test_scaled(self): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.scaled(s1, 2.0, 3.0, 4.0) + v2 = (1.0*2.0)*(2.0*3.0)*(3.0*4.0) + self.assert_(close(v2, s2.volume())) + + def test_scaledx(self): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.scaledx(s1, 2.0) + v2 = (1.0*2.0)*(2.0*1.0)*(3.0*1.0) + self.assert_(close(v2, s2.volume())) + + def test_scaledy(self): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.scaledy(s1, 2.0) + v2 = (1.0*1.0)*(2.0*2.0)*(3.0*1.0) + self.assert_(close(v2, s2.volume())) + + def test_scaledz(self): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = ccad.scaledz(s1, 2.0) + v2 = (1.0*1.0)*(2.0*1.0)*(3.0*2.0) + self.assert_(close(v2, s2.volume())) + + # reverse skipped *** + +# Face Functions + +# Solid Functions +class TestSolidFunctions(unittest.TestCase): + + def test_fuse(self): + s1 = ccad.sphere(1.0) + s2 = ccad.box(2.0, 2.0, 2.0) + s2.translate((0.0, -1.0, -1.0)) + s3 = ccad.fuse(s1, s2) + v3new = s3.volume() + s3 = ccad.old_fuse(s1, s2) + v3old = s3.volume() + s3 = s1 + s2 + v3op = s3.volume() + value = 2.0*2.0*2.0 + 0.5*4.0/3.0*math.pi*1.0**3 + self.assert_(close(v3new, value) and + close(v3old, value) and + close(v3op, value)) + + def test_cut(self): + s1 = ccad.sphere(1.0) + s2 = ccad.box(2.0, 2.0, 2.0) + s2.translate((0.0, -1.0, -1.0)) + s3 = ccad.cut(s1, s2) + v3new = s3.volume() + s3 = ccad.old_cut(s1, s2) + v3old = s3.volume() + s3 = s1 - s2 + v3op = s3.volume() + value = 0.5*4.0/3.0*math.pi*1.0**3 + self.assert_(close(v3new, value) and + close(v3old, value) and + close(v3op, value)) + + def test_common(self): + s1 = ccad.sphere(1.0) + v1 = s1.volume() + s2 = ccad.box(2.0, 2.0, 2.0) + s2.translate((0.0, -1.0, -1.0)) + s3 = ccad.common(s1, s2) + v3new = s3.volume() + s3 = ccad.old_common(s1, s2) + v3old = s3.volume() + s3 = s1 & s2 + v3op = s3.volume() + value = 0.5*4.0/3.0*math.pi*1.0**3 + self.assert_(close(v3new, value) and + close(v3old, value) and + close(v3op, value)) + + def test_fillet_fuse(self): + s1 = ccad.sphere(1.0) + s2 = ccad.box(4.0, 4.0, 4.0) + s2.translate((0.0, -2.0, -2.0)) + s3 = ccad.fillet_fuse(s1, s2, 0.5) + v3 = s3.volume() + #print v3, 4.0*4.0*4.0 + 0.5*4.0/3.0*math.pi*1.0**3 + # empirical + self.assert_(close(v3, 0.2 + 4.0*4.0*4.0 + 0.5*4.0/3.0*math.pi*1.0**3, 0.1)) + + def test_fillet_cut(self): + s1 = ccad.sphere(1.0) + s2 = ccad.box(4.0, 4.0, 4.0) + s2.translate((0.0, -2.0, -2.0)) + s3 = ccad.fillet_cut(s1, s2, 0.25) + v3 = s3.volume() + #print v3, 0.5*4.0/3.0*math.pi*1.0**3 + # empirical + self.assert_(close(v3, 0.5*4.0/3.0*math.pi*1.0**3 - 0.127, 0.1)) + + def test_fillet_common(self): + s1 = ccad.sphere(1.0) + s2 = ccad.box(4.0, 4.0, 4.0) + s2.translate((0.0, -2.0, -2.0)) + s3 = ccad.fillet_common(s1, s2, 0.25) + v3 = s3.volume() + #print v3, 0.5*4.0/3.0*math.pi*1.0**3 + # empirical + self.assert_(close(v3, 0.5*4.0/3.0*math.pi*1.0**3 - 0.127, 0.1)) + + def test_chamfer_fuse(self): + s1 = ccad.sphere(1.0) + s2 = ccad.box(4.0, 4.0, 4.0) + s2.translate((0.0, -2.0, -2.0)) + s3 = ccad.chamfer_fuse(s1, s2, 0.25) + v3 = s3.volume() + #print v3, 4.0*4.0*4.0 + 0.5*4.0/3.0*math.pi*1.0**3 + # empirical + self.assert_(close(v3, 0.2 + 4.0*4.0*4.0 + 0.5*4.0/3.0*math.pi*1.0**3, 0.1)) + + def test_chamfer_cut(self): + # sphere had trouble + s1 = ccad.box(2.0, 2.0, 2.0) + s1.translate((-1.0, -1.0, -1.0)) + s2 = ccad.box(4.0, 4.0, 4.0) + s2.translate((0.0, -2.0, -2.0)) + s3 = ccad.chamfer_cut(s1, s2, 0.25) + v3 = s3.volume() + #print v3, 0.5*2.0*2.0*2.0 + # empirical + self.assert_(close(v3, 0.5*2.0*2.0*2.0 - 0.229, 0.1)) + + def test_glue(self): + s1 = ccad.box(2.0, 2.0, 2.0) + s1.translate((-2.0, -1.0, -1.0)) + s2 = ccad.box(4.0, 4.0, 4.0) + s2.translate((0.0, -2.0, -2.0)) + f1 = s1.nearest('face', [(0.0, 0.0, 0.0)])[0] + f2 = s2.nearest('face', [(0.0, 0.0, 0.0)])[0] + s3 = ccad.glue(s1, s2, [(f1, f2)]) + v3 = s3.volume() + self.assert_(close(v3, 2.0*2.0*2.0 + 4.0*4.0*4.0)) + + def test_simple_glue(self): + s1 = ccad.box(2.0, 2.0, 2.0) + s1.translate((-2.0, -1.0, -1.0)) + s2 = ccad.box(2.0, 2.0, 2.0) + s2.translate((0.0, -1.0, -1.0)) + f1 = s1.nearest('face', [(0.0, 0.0, 0.0)])[0] + f2 = s2.nearest('face', [(0.0, 0.0, 0.0)])[0] + s3 = ccad.glue(s1, s2, [(f1, f2)]) + v3 = s3.volume() + self.assert_(close(v3, 2*2.0*2.0*2.0)) + +# Import Functions +class TestImportFunctions(unittest.TestCase): + + # test_to_brep covered by classes + + # test_to_iges covered by classes + + # test_to_step covered by classes + + def test_from_svg(self): + ws = ccad.from_svg('../doc/logo.svg') + total_length = 0.0 + for w in ws: + total_length = total_length + w.length() + print 'total_length', total_length + self.assert_(close(total_length, 2962.0, 1.0)) + +# Classes +# Philosophy: + +# 1. translate*, rotate*, mirror*, scale* are omitted, since they are +# one-liners which are mostly tested by TestShapeFunctions + +class TestVertex(unittest.TestCase): + + # inherited from shape + def test_to_brep(self): + s1 = ccad.vertex((1.0, 2.0, 3.0)) + r1 = s1.center() + s1.to_brep('tmp.brp') + s2 = ccad.from_brep('tmp.brp') + r2 = s2.center() + self.assert_(close(r1, (1.0, 2.0, 3.0)) and + close(r2, (1.0, 2.0, 3.0))) + + def test_to_iges(self): + s1 = ccad.vertex((1.0, 2.0, 3.0)) + r1 = s1.center() + s1.to_iges('tmp.igs', brep_mode = 1) + s2 = ccad.from_iges('tmp.igs') + r2 = s2.center() + self.assert_(close(r1, (1.0, 2.0, 3.0)) and + close(r2, (1.0, 2.0, 3.0))) + + # This broke. *** + #def test_to_step(self): + # s1 = ccad.vertex((1.0, 2.0, 3.0)) + # r1 = s1.center() + # s1.to_step('tmp.stp') + # s2 = ccad.from_step('tmp.stp') + # r2 = s2.center() + # self.assert_(close(r1, (1.0, 2.0, 3.0)) and + # close(r2, (1.0, 2.0, 3.0))) + + # subshapes skipped since there are no vertex subshapes + + def test_copy(self): + s1 = ccad.vertex((1.0, 2.0, 3.0)) + s2 = s1.copy() + s1.translate((1.0, 1.0, 1.0)) + self.assert_(close(s2.center(), (1.0, 2.0, 3.0)) and + close(s1.center(), (2.0, 3.0, 4.0))) + + def test_bounds(self): + s1 = ccad.vertex((1.0, 2.0, 3.0)) + self.assert_(close(s1.bounds(), (1.0, 2.0, 3.0, 1.0, 2.0, 3.0))) + + def test_center(self): + s1 = ccad.vertex((1.0, 2.0, 3.0)) + self.assert_(close(s1.center(), (1.0, 2.0, 3.0))) + + # subcenters skipped since there are no vertex subshapes + + def test_check(self): + s1 = ccad.vertex((1.0, 2.0, 3.0)) + self.assert_(s1.check()) + + def test_fix(self): + s1 = ccad.vertex((1.0, 2.0, 3.0)) + s1.fix() + self.assert_(close(s1.center(), (1.0, 2.0, 3.0))) + + def test_dump(self): + s1 = ccad.vertex((1.0, 2.0, 3.0)) + s1.dump() + self.assert_(True) + + # nearest skipped since there are no vertex subshapes + + # subtolerance skipped since there are no vertex subshapes + + # specific + + # center skipped since verified above + + def tolerance(self): + s1 = ccad.vertex((1.0, 2.0, 3.0)) + self.assert_(close(s1.tolerance(), 1e-7, eps = 1e-9)) + +class TestEdge(unittest.TestCase): + + # inherited from shape + def test_to_brep(self): + s1 = ccad.circle(1.0) + r1 = s1.length() + s1.to_brep('tmp.brp') + s2 = ccad.from_brep('tmp.brp') + r2 = s2.length() + self.assert_(close(r1, 2*math.pi) and + close(r2, 2*math.pi)) + + def test_to_iges(self): + s1 = ccad.circle(1.0) + r1 = s1.length() + s1.to_iges('tmp.igs', brep_mode = 1) + s2 = ccad.from_iges('tmp.igs') + r2 = s2.length() + self.assert_(close(r1, 2*math.pi) and + close(r2, 2*math.pi)) + + # This broke *** + #def test_to_step(self): + # s1 = ccad.circle(1.0) + # r1 = s1.length() + # s1.to_step('tmp.stp') + # s2 = ccad.from_step('tmp.stp') + # r2 = s2.length() + # self.assert_(close(r1, 2*math.pi) and + # close(r2, 2*math.pi)) + + def test_subshapes(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + vs = s1.subshapes('vertex') + self.assert_(len(vs) == 2 and + close(vs[0].center(), (0.0, 0.0, 0.0)) and + close(vs[1].center(), (1.0, 1.0, 1.0))) + + def test_copy(self): + s1 = ccad.circle(1.0) + s2 = s1.copy() + s1.translate((1.0, 1.0, 1.0)) + self.assert_(close(s2.center(), (0.0, 0.0, 0.0)) and + close(s1.center(), (1.0, 1.0, 1.0))) + + def test_bounds(self): + s1 = ccad.circle(1.0) + self.assert_(close(s1.bounds(), (-1.0, -1.0, 0.0, 1.0, 1.0, 0.0), eps = 0.1)) + + def test_center(self): + s1 = ccad.circle(1.0) + s1.translate((1.0, 2.0, 3.0)) + self.assert_(close(s1.center(), (1.0, 2.0, 3.0))) + + def test_subcenters(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + cs = s1.subcenters('vertex') + self.assert_(close(cs[0], (0.0, 0.0, 0.0)) and + close(cs[1], (1.0, 1.0, 1.0))) + + def test_check(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + self.assert_(s1.check()) + + def test_fix(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + s1.fix() + self.assert_(close(s1.center(), (0.5, 0.5, 0.5))) + + def test_dump(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + s1.dump() + self.assert_(True) + + def test_nearest(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + i1 = s1.nearest('vertex', [(1.0, 1.0, 1.0)])[0] + self.assert_(i1 == 1) + + def test_subtolerance(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + subtols = s1.subtolerance() + self.assert_(close(subtols, (1e-7, 1e-7, 1e-7), eps = 1e-9)) + + # specific + + # center skipped since verified above + + # length skipped since verified above + + def tolerance(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + self.assert_(close(s1.tolerance(), 1e-7, eps = 1e-9)) + + def type(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + self.assert_(s1.type() == 'line') + + def poly(self): + s1 = ccad.segment((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) + p1 = s1.poly() + self.assert_(len(p1) == 2 and + close(p1[0], (0.0, 0.0, 0.0)) and + close(p1[1], (1.0, 1.0, 1.0))) + +class TestWire(unittest.TestCase): + + # inherited from shape + def test_to_brep(self): + s1 = ccad.ngon(1.0, 3) + r1 = s1.length() + s1.to_brep('tmp.brp') + s2 = ccad.from_brep('tmp.brp') + r2 = s2.length() + self.assert_(close(r1, 5.196, eps = 1e-3) and + close(r2, 5.196, eps = 1e-3)) + + def test_to_iges(self): + s1 = ccad.ngon(1.0, 3) + r1 = s1.length() + s1.to_iges('tmp.igs', brep_mode = 1) + s2 = ccad.from_iges('tmp.igs') + r2 = s2.length() + self.assert_(close(r1, 5.196, eps = 1e-3) and + close(r2, 5.196, eps = 1e-3)) + + # This broke *** + #def test_to_step(self): + # s1 = ccad.ngon(1.0, 3) + # r1 = s1.length() + # s1.to_step('tmp.stp') + # s2 = ccad.from_step('tmp.stp') + # r2 = s2.length() + # self.assert_(close(r1, 5.196, eps = 1e-3) and + # close(r2, 5.196, eps = 1e-3)) + + def test_subshapes(self): + s1 = ccad.ngon(1.0, 3) + es = s1.subshapes('edge') + vs = s1.subshapes('vertex') + self.assert_(len(vs) == 3 and len(es) == 3) + + def test_copy(self): + s1 = ccad.ngon(1.0, 3) + s2 = s1.copy() + s1.translate((1.0, 1.0, 1.0)) + self.assert_(close(s2.center(), (0.0, 0.0, 0.0)) and + close(s1.center(), (1.0, 1.0, 1.0))) + + def test_bounds(self): + s1 = ccad.ngon(1.0, 3) + rt3d2 = math.sqrt(3.0)/2 + print 'bounds', s1.bounds() + self.assert_(close(s1.bounds(), (-0.5, -rt3d2, 0.0, 1.0, rt3d2, 0.0), eps = 0.1)) + + def test_center(self): + s1 = ccad.ngon(1.0, 3) + s1.translate((1.0, 2.0, 3.0)) + self.assert_(close(s1.center(), (1.0, 2.0, 3.0))) + + def test_subcenters(self): + s1 = ccad.ngon(1.0, 3) + cs = s1.subcenters('vertex') + rt3d2 = math.sqrt(3.0)/2 + self.assert_(close(cs[0], (-0.5, rt3d2, 0.0)) and + close(cs[1], (-0.5, -rt3d2, 0.0)) and + close(cs[2], (1.0, 0.0, 0.0))) + + def test_check(self): + s1 = ccad.ngon(1.0, 3) + self.assert_(s1.check()) + + def test_fix(self): + s1 = ccad.ngon(1.0, 3) + s1.translate((1.0, 2.0, 3.0)) + s1.fix() + self.assert_(close(s1.center(), (1.0, 2.0, 3.0))) + + def test_dump(self): + s1 = ccad.ngon(1.0, 3) + s1.dump() + self.assert_(True) + + def test_nearest(self): + s1 = ccad.ngon(1.0, 3) + i1 = s1.nearest('vertex', [(1.0, 0.0, 0.0)])[0] + self.assert_(i1 == 2) + + def test_subtolerance(self): + s1 = ccad.ngon(1.0, 3) + subtols = s1.subtolerance() + self.assert_(close(subtols, (1e-7, 1e-7, 1e-7), eps = 1e-9)) + + # specific + + # center skipped since verified above + + # length skipped since verified above + + def tolerance(self): + s1 = ccad.ngon(1.0, 3) + self.assert_(close(s1.tolerance(), 1e-7, eps = 1e-9)) + + def poly(self): + s1 = ccad.ngon(1.0, 3) + p1 = s1.poly() + cs = s1.subcenters('vertex') + self.assert_(len(p1) == 4 and + close(p1[0], cs[0]) and + close(p1[1], cs[1]) and + close(p1[2], cs[2]) and + close(p1[3], cs[3])) + +class TestFace(unittest.TestCase): + + # inherited from shape + def test_to_brep(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + r1 = s1.area() + s1.to_brep('tmp.brp') + s2 = ccad.from_brep('tmp.brp') + r2 = s2.area() + self.assert_(close(r1, r2)) + + def test_to_iges(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + r1 = s1.area() + s1.to_iges('tmp.igs', brep_mode = 1) + s2 = ccad.from_iges('tmp.igs') + r2 = s2.area() + self.assert_(close(r1, r2)) + + def test_to_step(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + r1 = s1.area() + s1.to_step('tmp.stp') + s2 = ccad.from_step('tmp.stp') + r2 = s2.area() + self.assert_(close(r1, r2)) + + def test_subshapes(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + ws = s1.subshapes('wire') + es = s1.subshapes('edge') + vs = s1.subshapes('vertex') + self.assert_(len(vs) == 3 and len(es) == 3 and len(ws) == 1) + + def test_copy(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + s2 = s1.copy() + s1.translate((1.0, 1.0, 1.0)) + self.assert_(close(s2.center(), (0.0, 0.0, 0.0)) and + close(s1.center(), (1.0, 1.0, 1.0))) + + def test_bounds(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + rt3d2 = math.sqrt(3.0)/2 + print 'bounds', s1.bounds() + self.assert_(close(s1.bounds(), (-0.5, -rt3d2, 0.0, 1.0, rt3d2, 0.0), eps = 0.1)) + + def test_center(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + s1.translate((1.0, 2.0, 3.0)) + self.assert_(close(s1.center(), (1.0, 2.0, 3.0))) + + def test_subcenters(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + cs = s1.subcenters('vertex') + rt3d2 = math.sqrt(3.0)/2 + self.assert_(close(cs[0], (-0.5, rt3d2, 0.0)) and + close(cs[1], (-0.5, -rt3d2, 0.0)) and + close(cs[2], (1.0, 0.0, 0.0))) + + def test_check(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + self.assert_(s1.check()) + + def test_fix(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + s1.translate((1.0, 2.0, 3.0)) + s1.fix() + self.assert_(close(s1.center(), (1.0, 2.0, 3.0))) + + def test_dump(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + s1.dump() + self.assert_(True) + + def test_nearest(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + i1 = s1.nearest('vertex', [(1.0, 0.0, 0.0)])[0] + self.assert_(i1 == 2) + + def test_subtolerance(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + subtols = s1.subtolerance() + self.assert_(close(subtols, (1e-7, 1e-7, 1e-7), eps = 1e-9)) + + # specific + + def test_fillet(self): + s = ccad.plane(ccad.ngon(1.0, 3)) + s1 = s.copy() + s1.fillet(0.2) + r1 = s1.area() + + s1 = s.copy() + s1.fillet(0.2, [0]) + r2 = s1.area() + + s1 = s.copy() + s1.fillet(0.2, [(1.0, 0.0, 0.0)]) + r3 = s1.area() + + s1 = s.copy() + s1.fillet([(0.1, [0]), + (0.2, [1]), + (0.3, [2])]) + r4 = s1.area() + # empirical + self.assert_(close(r1, 1.217, eps=0.001) and + close(r2, 1.272, eps=0.001) and + close(r3, 1.272, eps=0.001) and + close(r4, 1.203, eps=0.001)) + + def wire(self): + w1 = ccad.ngon(1.0, 3) + s1 = ccad.plane(w1) + w2 = s1.wire() + self.assert_(close(w1.length(), w2.length())) + + def inner_wires(self): + c1 = ccad.cylinder(1.0, 1.0) + c2 = ccad.cylinder(2.0, 1.0) + s1 = c2 - c1 + f1 = s1.nearest('face', [(0.0, 0.0, 1.0)])[0] + iw = f1.inner_wires()[0] + self.assert_(close(iw.length(), 2*math.pi*1.0)) + + # center skipped since verified above + + # area skipped since verified above + + def tolerance(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + self.assert_(close(s1.tolerance(), 1e-7, eps = 1e-9)) + + def type(self): + s1 = ccad.plane(ccad.ngon(1.0, 3)) + self.assert_(s1.type() == 'plane') + +class TestShell(unittest.TestCase): + + # inherited from shape + def test_to_brep(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + r1 = s1.area() + s1.to_brep('tmp.brp') + s2 = ccad.from_brep('tmp.brp') + r2 = s2.area() + self.assert_(close(r1, 22.0) and + close(r2, 22.0)) + + def test_to_iges(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + r1 = s1.area() + s1.to_iges('tmp.igs', brep_mode = 1) + s2 = ccad.from_iges('tmp.igs') + r2 = s2.area() + self.assert_(close(r1, 22.0) and + close(r2, 22.0)) + + def test_to_step(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + r1 = s1.area() + s1.to_step('tmp.stp') + s2 = ccad.from_step('tmp.stp') + r2 = s2.area() + self.assert_(close(r1, 22.0) and + close(r2, 22.0)) + + def test_subshapes(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + fs = s1.subshapes('face') + ws = s1.subshapes('wire') + es = s1.subshapes('edge') + vs = s1.subshapes('vertex') + self.assert_(len(vs) == 8 and len(es) == 12 and len(ws) == 6 and len(fs) == 6) + + def test_copy(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + s2 = s1.copy() + s1.translate((1.0, 1.0, 1.0)) + self.assert_(close(s2.center(), (0.5, 1.0, 1.5)) and + close(s1.center(), (1.5, 2.0, 2.5))) + + def test_bounds(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + self.assert_(close(s1.bounds(), (0.0, 0.0, 0.0, 1.0, 2.0, 3.0), eps = 0.1)) + + def test_center(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + self.assert_(close(s1.center(), (0.5, 1.0, 1.5))) + + def test_subcenters(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + cs = s1.subcenters('face') + center = [0.0, 0.0, 0.0] + for c in cs: + center[0] = center[0] + c[0] + center[1] = center[1] + c[1] + center[2] = center[2] + c[2] + self.assert_(close(center, (6.0*0.5, 6.0*1.0, 6.0*1.5))) + + def test_check(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + self.assert_(s1.check()) + + def test_fix(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + s1.fix() + self.assert_(close(s1.center(), (0.5, 1.0, 1.5))) + + def test_dump(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + s1.dump() + self.assert_(True) + + def test_nearest(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + i1 = s1.nearest('vertex', [(1.0, 2.0, 3.0)])[0] + self.assert_(close(s1.subshapes('vertex')[i1].center(), (1.0, 2.0, 3.0))) + + def test_subtolerance(self): + s1 = ccad.box(1.0, 2.0, 3.0).subshapes('shell')[0] + subtols = s1.subtolerance() + self.assert_(close(subtols, (1e-7, 1e-7, 1e-7), eps = 1e-9)) + + # specific + + # center skipped since verified above + + # area skipped since verified above + +class TestSolid(unittest.TestCase): + + # inherited from shape + def test_to_brep(self): + s1 = ccad.box(1.0, 2.0, 3.0) + r1 = s1.volume() + s1.to_brep('tmp.brp') + s2 = ccad.from_brep('tmp.brp') + r2 = s2.volume() + self.assert_(close(r1, 6.0) and + close(r2, 6.0)) + + def test_to_iges(self): + s1 = ccad.box(1.0, 2.0, 3.0) + r1 = s1.volume() + s1.to_iges('tmp.igs', brep_mode = 1) + s2 = ccad.from_iges('tmp.igs') + r2 = s2.volume() + self.assert_(close(r1, 6.0) and + close(r2, 6.0)) + + + def test_to_step(self): + s1 = ccad.box(1.0, 2.0, 3.0) + r1 = s1.volume() + s1.to_step('tmp.stp') + s2 = ccad.from_step('tmp.stp') + r2 = s2.volume() + self.assert_(close(r1, 6.0) and + close(r2, 6.0)) + + def test_subshapes(self): + s1 = ccad.box(1.0, 2.0, 3.0) + ss = s1.subshapes('shell') + fs = s1.subshapes('face') + ws = s1.subshapes('wire') + es = s1.subshapes('edge') + vs = s1.subshapes('vertex') + self.assert_(len(vs) == 8 and len(es) == 12 and len(ws) == 6 and len(fs) == 6 and len(ss) == 1) + + def test_copy(self): + s1 = ccad.box(1.0, 2.0, 3.0) + s2 = s1.copy() + s1.translate((1.0, 1.0, 1.0)) + self.assert_(close(s2.center(), (0.5, 1.0, 1.5)) and + close(s1.center(), (1.5, 2.0, 2.5))) + + def test_bounds(self): + s1 = ccad.box(1.0, 2.0, 3.0) + self.assert_(close(s1.bounds(), (0.0, 0.0, 0.0, 1.0, 2.0, 3.0), eps = 0.1)) + + def test_center(self): + s1 = ccad.box(1.0, 2.0, 3.0) + self.assert_(close(s1.center(), (0.5, 1.0, 1.5))) + + def test_subcenters(self): + s1 = ccad.box(1.0, 2.0, 3.0) + cs = s1.subcenters('face') + center = [0.0, 0.0, 0.0] + for c in cs: + center[0] = center[0] + c[0] + center[1] = center[1] + c[1] + center[2] = center[2] + c[2] + self.assert_(close(center, (6.0*0.5, 6.0*1.0, 6.0*1.5))) + + def test_check(self): + s1 = ccad.box(1.0, 2.0, 3.0) + self.assert_(s1.check()) + + def test_fix(self): + s1 = ccad.box(1.0, 2.0, 3.0) + s1.fix() + self.assert_(close(s1.center(), (0.5, 1.0, 1.5))) + + def test_dump(self): + s1 = ccad.box(1.0, 2.0, 3.0) + s1.dump() + self.assert_(True) + + def test_nearest(self): + s1 = ccad.box(1.0, 2.0, 3.0) + i1 = s1.nearest('vertex', [(1.0, 2.0, 3.0)])[0] + self.assert_(close(s1.subshapes('vertex')[i1].center(), (1.0, 2.0, 3.0))) + + def test_subtolerance(self): + s1 = ccad.box(1.0, 2.0, 3.0) + subtols = s1.subtolerance() + self.assert_(close(subtols, (1e-7, 1e-7, 1e-7), eps = 1e-9)) + + # specific + + # add skipped since verified in SolidFunctions + + # sub skipped since verified in SolidFunctions + + # and skipped since verified in SolidFunctions + + def test_to_stl(self): + s1 = ccad.box(1.0, 2.0, 3.0) + s1.to_stl('tmp.stl') + self.assert_(True) + + # center skipped since verified above + + def test_fillet(self): + s = ccad.box(1.0, 2.0, 3.0) + s1 = s.copy() + s1.fillet(0.2) + r1 = s1.volume() + + s2 = s.copy() + s2.fillet(0.2, [0]) + r2 = s2.volume() + + s3 = s.copy() + s3.fillet(0.2, [(1.0, 2.0, 1.5)]) + r3 = s3.volume() + + s4 = s.copy() + s4.fillet([(0.1, [0]), + (0.2, [1]), + (0.3, [2])]) + r4 = s4.volume() + # empirical + self.assert_(close(r1, 5.805, eps=0.001) and + close(r2, 5.974, eps=0.001) and + close(r3, 5.974, eps=0.001) and + close(r4, 5.920, eps=0.001)) + + def test_chamfer(self): + s = ccad.box(1.0, 2.0, 3.0) + s1 = s.copy() + s1.chamfer(0.2) + r1 = s1.volume() + + s2 = s.copy() + s2.chamfer(0.2, [0]) + r2 = s2.volume() + + s3 = s.copy() + s3.chamfer(0.2, [(1.0, 2.0, 1.5)]) + r3 = s3.volume() + + # empirical + self.assert_(close(r1, 5.563, eps=0.001) and + close(r2, 5.94, eps=0.001) and + close(r3, 5.94, eps=0.001)) + + # from documentation + def test_draft(self): + s1 = ccad.box(1.0, 1.0, 1.0) + s1.translate((-0.5, -0.5, 0.0)) + face_centers = s1.subcenters('face') + to_draft = [] + for count, face_center in enumerate(face_centers): + if abs(face_center[2] - 0.5) < 0.1: + to_draft.append(count) + s1.draft(math.radians(5.0), (0.0, 0.0, 1.0), (0.0, 0.0, 0.0), to_draft) + #print 'draft', s1.volume() + # empirical + self.assert_(close(s1.volume(), 0.835, eps=0.001)) + + # volume skipped since verified above + + # from documentation + def test_simplify(self): + s1 = ccad.box(1.0, 1.0, 1.0) + s2 = s1.copy() + s2.translate((1.0, 0.5, 0.5)) + s3 = s1 - s2 + count1 = len(s3.subshapes('face')) + s3.simplify() + count2 = len(s3.subshapes('face')) + self.assert_(count1 == 7 and count2 == 6) + +# Edge Primitives +class TestEdgePrimitives(unittest.TestCase): + + # from documentation + def test_segment(self): + pt1 = (0.0, 0.0, 0.0) + pt2 = (1.0, 0.0, 0.0) + e1 = ccad.segment(pt1, pt2) + self.assert_(close(1.0, e1.length())) + + # from documentation + def test_arc(self): + e1 = ccad.arc(1.0, 0.0, math.pi/2) + self.assert_(close(math.pi/2, e1.length())) + + # from documentation + def test_arc_ellipse(self): + e1 = ccad.arc_ellipse(2.0, 1.0, 0.0, math.pi/2) + # empirical + self.assert_(close(2.422, e1.length(), 0.001)) + + # from documentation + def test_spline(self): + pts = [(0.0, 0.0, 0.0), + (0.2, 0.1, 0.0), + (0.5, 0.2, 0.0), + (-0.5, 0.3, 0.0)] + e1 = ccad.spline(pts) + # empirical + self.assert_(close(1.864, e1.length(), 0.001)) + + # from documentation + def test_bezier(self): + e1 = ccad.bezier([(1.0, 0.0, 0.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 0.0)], [1.0, 1.0/math.sqrt(2.0), 1.0]) + self.assert_(close(math.pi/2, e1.length())) + + # from documentation + def test_circle(self): + e1 = ccad.circle(3.0) + self.assert_(close(3*2*math.pi, e1.length())) + + # from documenation + def test_ellipse(self): + e1 = ccad.ellipse(2.0, 1.0) + self.assert_(close(9.701, e1.length(), 0.001)) + +# Wire Primitives +class TestWirePrimitives(unittest.TestCase): + + # from documentation + def test_polygon(self): + w1 = ccad.polygon([(0.0, 0.0, 0.0), + (1.0, 0.0, 0.0), + (1.5, 1.0, 0.0), + (0.5, 1.5, 0.0), + (-0.5, -0.5, 0.0)]) + self.assert_(close(5.472, w1.length(), 0.001)) + + # from documentation + def test_rectangle(self): + w1 = ccad.rectangle(2.0, 1.0) + self.assert_(close(6.0, w1.length())) + + # from documentation + def test_ngon(self): + w1 = ccad.ngon(2.0, 6) + self.assert_(close(12.0, w1.length())) + + # from documentation + def test_helix(self): + w1 = ccad.helix(2.0, 1.0/math.pi, 3) + # empirical + self.assert_(close(39.710, w1.length(), 0.001)) + +# Face Primitives +class TestFacePrimitives(unittest.TestCase): + + # from documentation + def test_plane(self): + w1 = ccad.ngon(2.0, 5) + f1 = ccad.plane(w1) + self.assert_(close(9.511, f1.area(), 0.001)) + + # from documentation + def test_surface(self): + w1 = ccad.ngon(2.0, 8) + w2 = ccad.ngon(10.0, 4) + f2 = ccad.plane(w1) + f1 = ccad.face_from(f2, w2) + self.assert_(close(200.0, f1.area())) + + # from documentation + def test_filling(self): + e1 = ccad.spline([(0.0, 0.0, 0.0), + (1.0, 0.2, 0.3), + (1.5, 0.8, 1.0), + (0.8, 1.2, 0.2), + (0.0, 1.0, 0.0)]) + e2 = ccad.spline([(0.0, 0.0, 0.0), + (-1.0, 0.2, 0.3), + (-1.5, 0.8, 1.0), + (-0.8, 1.2, 0.2), + (0.0, 1.0, 0.0)]) + w1 = ccad.wire([e1, e2]) + f1 = ccad.filling(w1) + # empirical + self.assert_(close(5.448, f1.area(), 0.001)) + + # from documentation + def test_slice(self): + b1 = ccad.box(10.0, 10.0, 10.0) + b1.translate((-5.0, -5.0, 0.0)) + c1 = ccad.cylinder(2.5, 20.0) + c1.translate((0.0, 0.0, -5.0)) + s1 = b1 - c1 + f1 = ccad.slice(s1, z = 1.0)[0] + self.assert_(close(100.0-math.pi*2.5**2, f1.area(), 0.001)) + +# Solid Primitives +class TestSolidPrimitives(unittest.TestCase): + + # from documentation + def test_box(self): + s1 = ccad.box(1.0, 2.0, 3.0) + self.assert_(close(6.0, s1.volume())) + + def test_wedge(self): + s1 = ccad.wedge(1.0, 2.0, 3.0, 0.5) + s2 = ccad.wedge(1.0, 2.0, 3.0, 0.9, 1.1, 3.1, 3.2) + self.assert_(close(4.5, s1.volume()) and + close(7.98, s2.volume())) + + # from documenation + def test_cylinder(self): + s1 = ccad.cylinder(1.0, 2.0) + self.assert_(close(2.0*math.pi, s1.volume())) + + # from documenation + def test_sphere(self): + s1 = ccad.sphere(5.0) + self.assert_(close(4.0/3.0*math.pi*5**3, s1.volume())) + + # from documenation + def test_cone(self): + s1 = ccad.cone(4.0, 2.0, 2.0) + self.assert_(close(1.0/3.0*(math.pi*4.0**2*4.0 - math.pi*2.0**2*2.0), s1.volume())) + + # from documenation + def test_bezier_cone(self): + s1 = ccad.bezier_cone(4.0, 2.0, 2.0) + self.assert_(close(1.0/3.0*(math.pi*4.0**2*4.0 - math.pi*2.0**2*2.0), s1.volume())) + + # from documentation + def test_torus(self): + s1 = ccad.torus(10.0, 1.0) + # empirical + self.assert_(close(197.392, s1.volume(), 0.001)) + + # from documentation + def test_prism(self): + f1 = ccad.plane(ccad.ngon(2.0, 6)) + s1 = ccad.prism(f1, (0.0, 0.0, 1.0)) + # empirical + self.assert_(close(10.392, s1.volume(), 0.001)) + + # test_revol covered by bezier_cone + + # from documentation + def test_loft(self): + w1 = ccad.wire([ccad.circle(1.0)]) + w2 = ccad.wire([ccad.circle(2.0)]) + w2.translate((0.0, 0.0, 5.0)) + w3 = ccad.wire([ccad.circle(1.5)]) + w3.translate((0.0, 0.0, 10.0)) + s1 = ccad.loft([w1, w2, w3]) + # empirical + self.assert_(close(98.407, s1.volume(), 0.001)) + + def test_plane_loft(self): + w1 = ccad.ngon(1.0, 5) + w2 = ccad.ngon(2.0, 5) + w2.translate((0.0, 0.0, 4.0)) + s1 = ccad.plane_loft([w1, w2]) + # empirical + self.assert_(close(22.191, s1.volume(), 0.001)) + + # from documentation + def test_pipe(self): + profile = ccad.ngon(2.0, 6) + e1 = ccad.arc(8.0, 0.0, math.pi/2) + e2 = ccad.segment((0.0, 8.0, 0.0), (-8.0, 8.0, 0.0)) + spine = ccad.wire([e1, e2]) + spine.translate((-8.0, 0.0, 0.0)) + spine.rotatex(math.pi/2) + s1 = ccad.pipe(profile, spine) + # empirical + self.assert_(close(213.732, s1.volume(), 0.001)) + + # from documentation + def test_helical_solid(self): + profile = ccad.ngon(0.2, 3) + s1 = ccad.helical_solid(profile, 2.0, 1.0/math.pi, 2) + # empirical + self.assert_(close(1.346, s1.volume(), 0.001)) + +# Useful functions that return arbitrary shapes +class TestArbitrary(unittest.TestCase): + + # from documentation + def test_offset(self): + w1 = ccad.ngon(8.0, 6) + f1 = ccad.offset(ccad.plane(w1), 1.0)[0] + + b1 = ccad.box(10.0, 10.0, 10.0) + b1.translate((-5.0, -5.0, 0.0)) + c1 = ccad.cylinder(2.5, 20.0) + c1.translate((0.0, 0.0, -5.0)) + s1 = b1 - c1 + s2 = ccad.offset(s1, 1.0)[0] + + # empirical + self.assert_(close(217.418, f1.area(), 0.001) and + close(1608.966, s2.volume(), 0.001)) + + +def suite(tests = []): + suite = unittest.TestSuite() + if len(tests) == 0: # Do all + tests = filter(lambda x: x.startswith('Test'), globals()) + print tests + for test in tests: + eval('suite.addTest(unittest.makeSuite(' + test + '))') + return suite + +if __name__ == '__main__': + unittest.TextTestRunner(verbosity=2).run(suite(sys.argv[1:])) diff --git a/src/contrib/ccad/unittest/test_all.py b/src/contrib/ccad/unittest/test_all.py new file mode 100644 index 000000000..75d91ba4c --- /dev/null +++ b/src/contrib/ccad/unittest/test_all.py @@ -0,0 +1,22 @@ +""" +Description +----------- +ccad unittest. View ../model.py for a full description of ccad. + +Author +------ +Charles Sharman + +License +------- +Distributed under the GNU LESSER GENERAL PUBLIC LICENSE Version 3. +View LICENSE for details. +""" + +import unittest + +import model_unittest + +suite = unittest.TestSuite() +suite.addTests([model_unittest.suite()]) +unittest.TextTestRunner(verbosity=2).run(suite)