diff --git a/.gitignore b/.gitignore index eb8294a..65f6935 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,5 @@ presentations/ pycalculix.egg-info/ __pycache__ -# calculix docs -ccx_2.13.pdf -cgx_2.13.pdf +# tests +.pytest_cache diff --git a/Makefile b/Makefile index d28a21c..ef04575 100644 --- a/Makefile +++ b/Makefile @@ -19,6 +19,19 @@ clean_examples: rm -f ./examples/*.sta rm -f ./examples/*.out +clean: + rm -f ./*.fbd + rm -f ./*.inp + rm -f ./*.geo + rm -f ./*.msh + rm -f ./*.frd + rm -f ./*.dat + rm -f ./*.png + rm -f ./*.cvg + rm -f ./*.sta + rm -f ./*.out + + dist_examples: make clean_examples zip -r examples.zip examples @@ -28,6 +41,7 @@ dist_docs: rm -rf docs rm -rf documentation sphinx-apidoc -F -A "Justin Black" -o docs pycalculix + echo ' ' >> docs/conf.py echo 'from pycalculix.version import __version__' >> docs/conf.py echo 'version = __version__' >> docs/conf.py echo 'release = version' >> docs/conf.py @@ -48,10 +62,13 @@ dist_source: rm -rf *.egg-info develop: - python3 setup.py develop + pip3 install -e . install: - python3 setup.py install + pip3 install . uninstall: pip3 uninstall pycalculix + +test: + pytest tests/ diff --git a/README.md b/README.md index 9935a3d..20f5d3a 100644 --- a/README.md +++ b/README.md @@ -165,20 +165,24 @@ Setting element divisions on lines is supported - Gravity - Rotational speed forces - Displacement constraints -- Loads are stored on geometry primitives (points lines, areas) and can be -applied before or after meshing. +- Loads are stored on geometry primitives (points lines, areas) and +can be applied before or after meshing. ## Files Produced -Meshing and solving are done in the background using cgx or gmsh for meshing, and Calculix ccx for solving. +Meshing and solving are done in the background using cgx or gmsh for +meshing, and Calculix ccx for solving. Files Used: - .fbd (Calculix cgx gemetry file) - .inp (Calculix solver input file, or mesh definition) - .geo (Gmsh geometry file) - .msh (Gmsh native mesh file) -- .frd (Calculix ccx nodal results file, values are at nodes and were created by interpolating element integration point results back to the nodes) -- .dat (Calculix ccx element results file, includes integration point results) +- .frd (Calculix ccx nodal results file, values are at nodes and were + created by interpolating element integration point results back to + the nodes) +- .dat (Calculix ccx element results file, includes integration point + results) ## Uninstall @@ -210,8 +214,8 @@ python -m venv venv pip install -e . pycalculix-add-feaprograms ``` -- Now any changes that you make to your local version of pycalculix will be - live in your virtual environment +- Now any changes that you make to your local version of pycalculix + will be live in your virtual environment ## License See LICENSE.txt (Apache 2.0) @@ -224,15 +228,43 @@ Initial Release: December 2014 ## Change Log +#### 0.9.5 (github only) +- Currently in branch 'feature/adds_samples_testing' +- Adds tests: sample tests at tests/test_samples.py +- Adds tests: meshing tests at tests/test_meshing.py +- Adds solving and meshing timeout exception to capture when they hang +- Fixes dxf import feature, syntax updates to use dfxgrabber >= 0.8.0, + Issue 32 +- Adds requirement for dfxgrabber >= 0.8.0 to ensure that dxf import works +- Pegs Mac gmsh version to gmsh == 3.0.5 because version 3.0.6 + throws segault errors when meshing +- Fixes a bug where solver input file does not write material before + time steps, Issue 32 +- Fixed ccx installer on Windows: zip file is now found and + downloaded +- Throws an exception if ccx version is too old, v 2.7 and earlier is + too old +- Pegs Win gmsh install version to 3.0.5 +- Updates the calculation for element Seqv, S1, S2, and S3 avg max + and min values. Now calculates Seqv and principal stresses at all + integration points, then calculates the avg max and min of those + values +- Win pegged ccx to version 2.12 +- Mac brew brewsci/science/calculix-ccx is currently at ccx version 2.13 +- Ubuntu apt-get calculix-ccx is currently is currently at version 2.11 + #### 0.9.4 (github only) - removed gmsh and calculix -- moved dist and documentation building and example cleanup into make file +- moved dist and documentation building and example cleanup into make + file - changed the license to Apache 2.0 -- added command line tool to install/uninstall gmsh and ccx for windows/mac os x/ubuntu +- added command line tool to install/uninstall gmsh and ccx for + windows/mac os x/ubuntu - pycalculix-add-feaprograms - pycalculix-remove-feaprograms - added requests library requirement for pycalculix-add-feaprograms -- fixed bug where frd files could no longer be read +- fixed bug where frd files could no longer be read because Calculix + results keywords changed since initial 2014 release #### 0.9.3 - ADDED: multiple parts with contacts @@ -274,17 +306,13 @@ Initial Release: December 2014 - pycalculix.FeaModel.make_problem or pycalculix.Problem - Make Results File: - pycalculix.Problem.rfile or pycalculix.ResultsFile(problem) -- FIX: Plotting fix, closing triangles in the correct direction in matplotlib +- FIX: Plotting fix, closing triangles in the correct direction in + matplotlib - DOC: All code separated into modules for clarity. - DOC: Docstrings added to all classes + methods + functions -- PLOTTING: Closed areas are now filled in yellow when plotting geometry. +- PLOTTING: Closed areas are now filled in yellow when plotting + geometry. - PLOTTING: Signed line names are shown and internal to the area. - BACKEND: Implemented signed line and signed arc class. - Pressures can now be applied on these signed lines. - Many methods and variables made private to clean up name space. - -## Developer Todo: -- confirm that set_ediv still works, remove the past merge if that broke it -- add images to the readme -- add new release on github release branch -- add new release on pypi diff --git a/dist/documentation.zip b/dist/documentation.zip index cf7c910..f21f476 100644 Binary files a/dist/documentation.zip and b/dist/documentation.zip differ diff --git a/dist/examples.zip b/dist/examples.zip index 1781602..356b7b3 100644 Binary files a/dist/examples.zip and b/dist/examples.zip differ diff --git a/dist/pycalculix-0.9.4.zip b/dist/pycalculix-0.9.4.zip deleted file mode 100644 index 95cae79..0000000 Binary files a/dist/pycalculix-0.9.4.zip and /dev/null differ diff --git a/dist/pycalculix-0.9.5.zip b/dist/pycalculix-0.9.5.zip new file mode 100644 index 0000000..c4c30f3 Binary files /dev/null and b/dist/pycalculix-0.9.5.zip differ diff --git a/examples/Pycalculix_Overview_2014-12-22.pdf b/examples/Pycalculix_Overview_2014-12-22.pdf deleted file mode 100644 index debd582..0000000 Binary files a/examples/Pycalculix_Overview_2014-12-22.pdf and /dev/null differ diff --git a/examples/compr-rotor.py b/examples/compr-rotor.py index e27aebd..f6595a9 100644 --- a/examples/compr-rotor.py +++ b/examples/compr-rotor.py @@ -1,12 +1,24 @@ #!/usr/bin/env python3 +import math +import sys + import pycalculix as pyc import matplotlib.pyplot as plt -import math # We'll be modeling a rotating jet engine part model_name = 'compr-rotor' model = pyc.FeaModel(model_name) -model.set_units('m') # this sets dist units to meters, labels our consistent units +# this sets dist units to meters, labels our consistent units +model.set_units('m') + +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' # problem + geometry constants rpm = 1000 # rotor speed in rpm @@ -32,11 +44,15 @@ flowpath_ax = (web_width + 2*arm_length - airfoil_width) / 2.0 # these are deltas -disk_loop = [[disk_ht,0],[diskramp_rad,diskramp_ax],[web_ht,0,],[0,-arm_length], - [arm_th,0],[0,flowpath_ax],[0,airfoil_width],[0,flowpath_ax], - [-arm_th,0],[0,-arm_length],[-web_ht,0],[-diskramp_rad,diskramp_ax], +disk_loop = [[disk_ht,0],[diskramp_rad,diskramp_ax], + [web_ht,0,],[0,-arm_length], + [arm_th,0],[0,flowpath_ax], + [0,airfoil_width],[0,flowpath_ax], + [-arm_th,0],[0,-arm_length],[-web_ht,0], + [-diskramp_rad,diskramp_ax], [-disk_ht,0],[0,-disk_width]] -airfoil_loop = [[airfoil_ht,0],[0,airfoil_width],[-airfoil_ht,0],[0,-airfoil_width]] +airfoil_loop = [[airfoil_ht,0],[0,airfoil_width], + [-airfoil_ht,0],[0,-airfoil_width]] # make part part = pyc.Part(model) @@ -60,7 +76,8 @@ for [i1, i2, rad] in fillet_list: part.fillet_lines(lines[i1], lines[i2], rad) # view the geometry -model.plot_geometry(model_name+'_geom', pnum=False, lnum=False) +model.plot_geometry(model_name+'_geom', pnum=False, lnum=False, + display=show_gui) # set loads and constraints model.set_rpm(10000, part) @@ -72,7 +89,7 @@ model.set_matl(mat, part) # mesh model -model.set_eshape('quad', 2) +model.set_eshape(eshape, 2) model.set_etype('axisym', 'A0') model.set_etype('plstress', 'A1', 0.1) model.get_item('L15').set_ediv(8) @@ -80,8 +97,9 @@ model.get_item('L2').set_ediv(24) model.get_item('L10').set_ediv(24) model.mesh(1.0, 'gmsh') # mesh with 1.0 fineness, smaller is finer -model.plot_elements(model_name+'_elem') # plot the part elements -model.plot_constraints(model_name+'_constr') +# plot the part elements +model.plot_elements(model_name+'_elem', display=show_gui) +model.plot_constraints(model_name+'_constr', display=show_gui) # make and solve the model prob = pyc.Problem(model, 'struct') diff --git a/examples/dam-eplot.py b/examples/dam-eplot.py index d68e088..344e1e6 100644 --- a/examples/dam-eplot.py +++ b/examples/dam-eplot.py @@ -1,13 +1,25 @@ #!/usr/bin/env python3 -import pycalculix as pyc import math +import sys + +import pycalculix as pyc # We'll be modeling a masonry gravity dam, the Beetaloo dam in Australia # This time, we'll include multiple time steps, and element plotting # make model model_name = 'dam-eplot' model = pyc.FeaModel(model_name) -model.set_units('m') # this sets dist units to meters, labels our consistent units +# this sets dist units to meters, labels our consistent units +model.set_units('m') + +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' # Problem constants grav = 9.81 # m/s^2 @@ -39,7 +51,7 @@ for [x,y] in pts_ft_air: [x,y] = [x*0.3048,y*0.3048] # conversion to metric [L1,p1,p2] = part.draw_line_to(x, y) - air_lines.append(L1) + air_lines.append(L1) # make the two arcs pts_ft_arcs = [ [[22,73],[146,208]], [[14,98],[41,93]] ] for [[x,y],[xc,yc]] in pts_ft_arcs: @@ -54,7 +66,8 @@ [L1,p1,p2] = part.draw_line_to(x, y) air_lines.append(L1) part.draw_line_to(0, 0) -model.plot_geometry(model_name+'_geom') # view the points, lines, and areas +# view the points, lines, and areas +model.plot_geometry(model_name+'_geom', display=show_gui) # set part material mat = pyc.Material('concrete') @@ -62,20 +75,22 @@ model.set_matl(mat, part) # set the element type, line division, and mesh the database -model.set_eshape('quad', 2) +model.set_eshape(eshape, 2) model.set_etype('plstrain', part, thickness) model.set_ediv('L8',2) -model.mesh(0.5, 'gmsh') # mesh with 1.0 or less fineness, smaller is finer -model.plot_elements(model_name+'_elem') # plot the part elements +# mesh with 1.0 or less fineness, smaller is finer +model.mesh(0.5, 'gmsh') +model.plot_elements(model_name+'_elem', display=show_gui) # plot the part elements # set loads and constraints model.set_load('press', air_lines, press_atm) -model.set_fluid_press(water_lines, dens_water, grav, water_ht_m, press_atm) +model.set_fluid_press(water_lines, dens_water, grav, water_ht_m, + press_atm) model.set_gravity(grav, part) model.set_constr('fix', part.bottom, 'x') model.set_constr('fix', part.bottom, 'y') -model.plot_pressures(model_name+'_press_1') -model.plot_constraints(model_name+'_constr') +model.plot_pressures(model_name+'_press_1', display=show_gui) +model.plot_constraints(model_name+'_constr', display=show_gui) # make model and solve it prob = pyc.Problem(model, 'struct') @@ -87,7 +102,7 @@ fields = fields.split(',') for time in prob.rfile.steps: - prob.rfile.set_time(time) + prob.rfile.set_time(time) for field in fields: fname = '%s_%i_%s' % (model_name, int(time), field) # nodal plotting diff --git a/examples/dam-times.py b/examples/dam-times.py index ad4e0a3..086433f 100644 --- a/examples/dam-times.py +++ b/examples/dam-times.py @@ -1,13 +1,25 @@ #!/usr/bin/env python3 -import pycalculix as pyc import math +import sys + +import pycalculix as pyc # We'll be modeling a masonry gravity dam, the Beetaloo dam in Australia # This time, we'll include multiple time steps # make model model_name = 'dam-times' model = pyc.FeaModel(model_name) -model.set_units('m') # this sets dist units to meters, labels our consistent units +# this sets dist units to meters, labels our consistent units +model.set_units('m') + +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' # Problem constants grav = 9.81 # m/s^2 @@ -39,7 +51,7 @@ for [x,y] in pts_ft_air: [x,y] = [x*0.3048,y*0.3048] # conversion to metric [L1,p1,p2] = part.draw_line_to(x, y) - air_lines.append(L1) + air_lines.append(L1) # make the two arcs pts_ft_arcs = [ [[22,73],[146,208]], [[14,98],[41,93]] ] for [[x,y],[xc,yc]] in pts_ft_arcs: @@ -54,7 +66,8 @@ [L1,p1,p2] = part.draw_line_to(x, y) air_lines.append(L1) part.draw_line_to(0, 0) -model.plot_geometry(model_name+'_geom') # view the points, lines, and areas +# view the points, lines, and areas +model.plot_geometry(model_name+'_geom', display=show_gui) # set part material mat = pyc.Material('concrete') @@ -65,27 +78,30 @@ model.set_eshape('quad', 2) model.set_etype('plstrain', part, thickness) model.set_ediv('L8',2) -model.mesh(0.5, 'gmsh') # mesh with 1.0 or less fineness, smaller is finer -model.plot_elements(model_name+'_elem') # plot the part elements +# mesh with 1.0 or less fineness, smaller is finer +model.mesh(0.5, 'gmsh') +# plot the part elements +model.plot_elements(model_name+'_elem', display=show_gui) # set loads and constraints, all loads at first are at Time = 0s model.set_load('press', air_lines, press_atm) -model.set_fluid_press(water_lines, dens_water, grav, water_ht_m, press_atm) +model.set_fluid_press(water_lines, dens_water, grav, water_ht_m, + press_atm) model.set_gravity(grav, part) model.set_constr('fix', part.bottom, 'x') model.set_constr('fix', part.bottom, 'y') -model.plot_pressures(model_name+'_press_1') -model.plot_constraints(model_name+'_constr') +model.plot_pressures(model_name+'_press_1', display=show_gui) +model.plot_constraints(model_name+'_constr', display=show_gui) # Time = 2s, ambient pressure and gravity model.set_time(2.0) model.set_load('press', water_lines+air_lines, press_atm) -model.plot_pressures(model_name+'_press_2') +model.plot_pressures(model_name+'_press_2', display=show_gui) # Time = 3s, gravity only model.set_time(3.0) model.set_load('press', water_lines+air_lines, 0.0) -model.plot_pressures(model_name+'_press_3') +model.plot_pressures(model_name+'_press_3', display=show_gui) # make model and solve it prob = pyc.Problem(model, 'struct') @@ -96,7 +112,8 @@ fields = 'S1,S2,S3,Seqv,Sx,Sy' # store the fields to write fields = fields.split(',') -# store max and min values so plots can have the same max and min over all times +# store max and min values so plots can have the same max and min +# over all times max_val, min_val = {}, {} for field in fields: max_vals = [prob.rfile.get_emax(field, time) for time in prob.rfile.steps] @@ -115,4 +132,4 @@ fname = '%s_%s_%i' % (model_name, field, int(time)) vmax, vmin = max_val[field], min_val[field] prob.rfile.eplot(field, fname, display=disp, - max_val=vmax, min_val=vmin, title=titles[time]) \ No newline at end of file + max_val=vmax, min_val=vmin, title=titles[time]) diff --git a/examples/dam.py b/examples/dam.py index 9110812..765d485 100644 --- a/examples/dam.py +++ b/examples/dam.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 -import pycalculix as pyc import math +import sys + +import pycalculix as pyc # We'll be modeling a masonry gravity dam, the Beetaloo dam in Australia # Problem constants @@ -12,6 +14,15 @@ water_ht_ft = 109 length_dam_ft = 580 +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + # derived dims water_ax = (water_ht_ft-22)/math.tan(math.radians(89)) + 12 top_ax = (dam_ht_ft-22)/math.tan(math.radians(89)) + 12 @@ -24,7 +35,8 @@ # make model model = pyc.FeaModel(proj_name) -model.set_units('m') # this sets dist units to meters, labels our consistent units +# this sets dist units to meters, labels our consistent units +model.set_units('m') # make part, coordinates are x, y = radial, axial part = pyc.Part(model) @@ -53,7 +65,8 @@ [L1,p1,p2] = part.draw_line_to(x, y) air_lines.append(L1) part.draw_line_to(0, 0) -model.plot_geometry(proj_name+'_geom') # view the points, lines, and areas +# view the points, lines, and areas +model.plot_geometry(proj_name+'_geom', display=show_gui) # set part material mat = pyc.Material('concrete') @@ -61,28 +74,32 @@ model.set_matl(mat, part) # set the element type, line division, and mesh the database -model.set_eshape('quad', 2) +model.set_eshape(eshape, 2) model.set_etype('plstrain', part, thickness) model.get_item('L8').set_ediv(2) -model.mesh(0.5, 'gmsh') # mesh with 1.0 or less fineness, smaller is finer -model.plot_elements(proj_name+'_elem') # plot the part elements +# mesh with 1.0 or less fineness, smaller is finer +model.mesh(0.5, 'gmsh') +# plot the part elements +model.plot_elements(proj_name+'_elem', display=show_gui) # set loads and constraints model.set_load('press', air_lines, press_atm) -model.set_fluid_press(water_lines, dens_water, grav, water_ht_m, press_atm) +model.set_fluid_press(water_lines, dens_water, grav, water_ht_m, + press_atm) model.set_gravity(grav, part) model.set_constr('fix', part.bottom, 'x') model.set_constr('fix', part.bottom, 'y') -model.plot_pressures(proj_name+'_press_1') -model.plot_constraints(proj_name+'_constr') +model.plot_pressures(proj_name+'_press_1', display=show_gui) +model.plot_constraints(proj_name+'_constr', display=show_gui) # make model and solve it prob = pyc.Problem(model, 'struct') prob.solve() # query results and store them -disp = False # turn off display plotting -fields = 'Seqv,Sx,Sy,Sz,S1,S2,S3,ux,uy,utot' # store the fields to write +disp = False # turn off display plotting +# store the fields to write +fields = 'Seqv,Sx,Sy,Sz,S1,S2,S3,ux,uy,utot' fields = fields.split(',') for field in fields: fname = proj_name+'_'+field diff --git a/examples/hole-fancy.py b/examples/hole-fancy.py index 1bb5ad2..02b3255 100644 --- a/examples/hole-fancy.py +++ b/examples/hole-fancy.py @@ -1,10 +1,22 @@ #!/usr/bin/env python3 +import sys + import pycalculix as pyc # arc test sample proj_name = 'hole-fancy' model = pyc.FeaModel(proj_name) -model.set_units('m') # this sets dist units to meters, labels our consistent units + # this sets dist units to meters, labels our consistent units +model.set_units('m') + +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' # Define variables we'll use to draw part geometry length = 8 @@ -13,7 +25,8 @@ hole_width = width - 2*radius hole_length = length - 2*radius -# Draw part geometry, you must draw the part CLOCKWISE, x, y = radial, axial +# Draw part geometry, you must draw the part CLOCKWISE +# x, y = radial, axial part = pyc.Part(model) part.goto(length*0.5, -width*0.5) part.draw_line_ax(width) @@ -35,9 +48,10 @@ model.set_ediv(arcs, 10) part.chunk() -model.plot_geometry(pnum=False, lnum=False) # view the geometry +# view the geometry +model.plot_geometry(pnum=False, lnum=False, display=show_gui) model.set_etype('plstress', part, 0.01) -model.set_eshape('quad', 2) +model.set_eshape(eshape, 2) model.mesh(0.7, 'gmsh') -model.plot_elements() +model.plot_elements(display=show_gui) diff --git a/examples/hole-in-plate-full.py b/examples/hole-in-plate-full.py index 51b2219..258bb03 100644 --- a/examples/hole-in-plate-full.py +++ b/examples/hole-in-plate-full.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +import sys + import pycalculix as pyc # Vertical hole in plate model, make model @@ -6,6 +8,15 @@ model = pyc.FeaModel(proj_name) model.set_units('m') # this sets dist units to meters +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + # Define variables we'll use to draw part geometry diam = 2.0 # hole diam ratio = 0.45 @@ -13,7 +24,8 @@ print('D=%f, H=%f, D/H=%f' % (diam, width, diam/width)) length = 2*width #plate length -# Draw part geometry, you must draw the part CLOCKWISE, x, y = radial, axial +# Draw part geometry, you must draw the part CLOCKWISE, +# x, y = radial, axial part = pyc.Part(model) part.goto(length*0.5, -width*0.5) part.draw_line_ax(width) @@ -23,7 +35,8 @@ hole_lines = part.draw_hole(0, 0, diam*0.5, filled=False) model.set_ediv(hole_lines, 10) part.chunk() -model.plot_geometry(proj_name+'_geom') # view the geometry +# view the geometry +model.plot_geometry(proj_name+'_geom', display=show_gui) # set loads and constraints pressure = -1000 @@ -38,12 +51,12 @@ model.set_matl(mat, part) # set the element type and mesh database -model.set_eshape('quad', 2) +model.set_eshape(eshape, 2) model.set_etype('plstress', part, 0.1) model.mesh(1.0, 'gmsh') # mesh 1.0 fineness, smaller is finer -model.plot_elements(proj_name+'_elem') # plot part elements -model.plot_pressures(proj_name+'_press') -model.plot_constraints(proj_name+'_constr') +model.plot_elements(proj_name+'_elem', display=show_gui) +model.plot_pressures(proj_name+'_press', display=show_gui) +model.plot_constraints(proj_name+'_constr', display=show_gui) # make and solve the model prob = pyc.Problem(model, 'struct') @@ -54,7 +67,8 @@ print('Sx_max: %f' % sx) # Plot results -fields = 'Sx,Sy,S1,S2,S3,Seqv,ux,uy,utot,ex' # store the fields to plot +# store the fields to plot +fields = 'Sx,Sy,S1,S2,S3,Seqv,ux,uy,utot,ex' fields = fields.split(',') for field in fields: fname = proj_name+'_'+field diff --git a/examples/hole-in-plate-quarter.py b/examples/hole-in-plate-quarter.py index 4e1de38..3ff336c 100644 --- a/examples/hole-in-plate-quarter.py +++ b/examples/hole-in-plate-quarter.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +import sys + import pycalculix as pyc # Vertical hole in plate model, make model @@ -6,6 +8,15 @@ model = pyc.FeaModel(proj_name) model.set_units('m') # this sets dist units to meters +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + # Define variables we'll use to draw part geometry diam = 2.000 # hole diam width = 4.444 # plate width @@ -25,9 +36,9 @@ part.draw_line_rad(-length_quarter*0.5) part.draw_line_rad(-length_quarter*0.5) part.draw_line_to(0.0, rad) -model.plot_geometry(proj_name+'_A0') # view the geometry +model.plot_geometry(proj_name+'_A0', display=show_gui) part.chunk() # cut the part into area pieces so CGX can mesh it -model.plot_geometry(proj_name+'_A0chunked') # view the geometry +model.plot_geometry(proj_name+'_A0chunked', display=show_gui) # set loads and constraints model.set_load('press',part.top,-1000) @@ -40,13 +51,13 @@ model.set_matl(mat, part) # set the element type and mesh database -model.set_eshape('quad', 2) +model.set_eshape(eshape, 2) model.set_etype('plstress', part, 0.1) model.set_ediv('L0', 20) # set element divisions model.mesh(1.0, 'gmsh') # mesh 1.0 fineness, smaller is finer -model.plot_elements(proj_name+'_elem') # plot part elements -model.plot_pressures(proj_name+'_press') -model.plot_constraints(proj_name+'_constr') +model.plot_elements(proj_name+'_elem', display=show_gui) +model.plot_pressures(proj_name+'_press', display=show_gui) +model.plot_constraints(proj_name+'_constr', display=show_gui) # make and solve the model prob = pyc.Problem(model, 'struct') @@ -56,10 +67,12 @@ sx = prob.rfile.get_nmax('Sx') print('Sx_max: %f' % sx) [fx, fy, fz] = prob.rfile.get_fsum(model.get_item('L5')) -print('Reaction forces (fx,fy,fz) = (%12.10f, %12.10f, %12.10f)' % (fx, fy, fz)) +print('Reaction forces (fx,fy,fz) = (%12.10f, %12.10f, %12.10f)' % + (fx, fy, fz)) # Plot results -fields = 'Sx,Sy,S1,S2,S3,Seqv,ux,uy,utot,ex' # store the fields to plot +# store the fields to plot +fields = 'Sx,Sy,S1,S2,S3,Seqv,ux,uy,utot,ex' fields = fields.split(',') for field in fields: fname = proj_name+'_'+field diff --git a/examples/hole-kt-study.py b/examples/hole-kt-study.py index eaea039..ddb5bad 100644 --- a/examples/hole-kt-study.py +++ b/examples/hole-kt-study.py @@ -1,14 +1,24 @@ #!/usr/bin/env python3 -import pycalculix as pyc -import matplotlib.pyplot as plt import math import numpy as np +import sys + +import pycalculix as pyc +import matplotlib.pyplot as plt + +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' # Stress and geometry constants stress_val = 1000 diam = 1.0 thickness = 0.01 -disp = False # sets whether or not to display images # Make a list of geometry ratios, diam_hole/width_plate ratios = np.arange(0,.5,.05) @@ -38,7 +48,7 @@ def kt_peterson(ratio): model_name = 'hole-kt-study' model = pyc.FeaModel(model_name) model.set_units('m') # this sets dist units to meters - + # make part, coordinates are x, y = radial, axial part = pyc.Part(model) part.goto(0.0,rad) @@ -48,32 +58,33 @@ def kt_peterson(ratio): part.draw_line_rad(-right*.5) part.draw_line_rad(-right*.5) #this point lets us chunks our area part.draw_line_ax(-bot) - # part.plot_geometry('hole_kt_prechunk', display=disp) # view geometry + # part.plot_geometry('hole_kt_prechunk', display=disp) part.chunk() - model.plot_geometry(model_name+'_chunked', display=disp) # view geometry - + model.plot_geometry(model_name+'_chunked', display=False) + # set loads and constraints model.set_load('press',part.top,-1*stress_val) model.set_constr('fix',part.left,'y') model.set_constr('fix',part.bottom,'x') - + # set part material mat = pyc.Material('steel') mat.set_mech_props(7800, 210000, 0.3) - model.set_matl(mat, part) - + model.set_matl(mat, part) + # set the element type, line division, and mesh the database ediv = 19 model.set_ediv('L0',ediv) # sets # of elements on the arc - - model.set_eshape('tri', 2) + + model.set_eshape(eshape, 2) model.set_etype('plstress', part, thickness) - model.mesh(1.0, 'gmsh') # mesh with 1.0 fineness, smaller is finer - model.plot_elements('%s_elem_%.3f' % (model_name, ratio), display=disp) - model.plot_pressures('%s_press' % (model_name), display=disp) + model.mesh(1.0, 'gmsh') # mesh with 1.0 fineness, smaller is finer + model.plot_elements('%s_elem_%.3f' % (model_name, ratio), + display=False) + model.plot_pressures('%s_press' % (model_name), display=False) # make model and solve it - prob = pyc.Problem(model, 'struct') + prob = pyc.Problem(model, 'struct') prob.solve() # query results and store them @@ -83,7 +94,7 @@ def kt_peterson(ratio): ktg_pet.append(kt_peterson(ratio)) error = 100*(kt_fea/kt_peterson(ratio) - 1) err.append(error) - print('For ratio %3f, Kt_g = %3.2f' % (ratio, kt_fea)) + print('For ratio %3f, Kt_g = %3.2f' % (ratio, kt_fea)) # plot results fig, ax = plt.subplots() @@ -94,7 +105,8 @@ def kt_peterson(ratio): plt.title('Tension Hole in Plate Stress Concentration Factor, Ktg') plt.xlabel('D/h') plt.ylabel('Ktg') -pyc.base_classes.plot_finish(plt, fname=model_name+'_kts', display=True) +pyc.base_classes.plot_finish(plt, fname=model_name+'_kts', + display=show_gui) # plot error fig, ax = plt.subplots() @@ -104,4 +116,5 @@ def kt_peterson(ratio): plt.title('Tension Hole in Plate Ktg Error, FEA vs Peterson') plt.xlabel('D/h') plt.ylabel('Error (%)') -pyc.base_classes.plot_finish(plt, fname=model_name+'_error', display=True) +pyc.base_classes.plot_finish(plt, fname=model_name+'_error', + display=show_gui) diff --git a/examples/kontrola.dxf b/examples/import-dxf-1.dxf similarity index 100% rename from examples/kontrola.dxf rename to examples/import-dxf-1.dxf diff --git a/examples/import-dxf-1.py b/examples/import-dxf-1.py new file mode 100644 index 0000000..c412e4c --- /dev/null +++ b/examples/import-dxf-1.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import os +import sys + +import pycalculix as pyc + +model_name = 'import-dxf-1' +model = pyc.FeaModel(model_name) +model.set_units('m') + +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + +abs_path = os.path.dirname(os.path.abspath(__file__)) +fname = os.path.join(abs_path, '%s.dxf' % model_name) +importer = pyc.CadImporter(model, fname, swapxy=True) +parts = importer.load() +model.plot_geometry(model_name+'_imported', display=show_gui) +#parts[0].chunk() +model.plot_geometry(model_name+'_areas', pnum=False, + lnum=False, display=show_gui) +model.plot_geometry(model_name+'_lines', anum=False, + pnum=False, display=show_gui) +model.plot_geometry(model_name+'_points', anum=False, + lnum=False, display=show_gui) + + +model.view.print_summary() + +model.set_etype('axisym', parts) +model.set_eshape(eshape, 2) +model.mesh(1.0, 'gmsh') +model.plot_elements(model_name+'_elements', display=show_gui) +model.view.print_summary() diff --git a/examples/import-dxf-2.dxf b/examples/import-dxf-2.dxf new file mode 100644 index 0000000..86c1098 --- /dev/null +++ b/examples/import-dxf-2.dxf @@ -0,0 +1,3934 @@ + 0 +SECTION + 2 +BLOCKS + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +LINE + 8 +0 + 10 +60.25 + 20 +14.5404 + 11 +60.25 + 21 +24.1874 + 0 +LINE + 8 +0 + 10 +60.25 + 20 +24.1874 + 11 +67.375 + 21 +21.5942 + 0 +LINE + 8 +0 + 10 +67.375 + 20 +21.5942 + 11 +67.75 + 21 +21.8107 + 0 +LINE + 8 +0 + 10 +67.75 + 20 +21.8107 + 11 +67.75 + 21 +31.8107 + 0 +LINE + 8 +0 + 10 +67.75 + 20 +31.8107 + 11 +67.375 + 21 +32.0271 + 0 +LINE + 8 +0 + 10 +67.375 + 20 +32.0271 + 11 +60.5 + 21 +29.5249 + 0 +LINE + 8 +0 + 10 +60.5 + 20 +29.5249 + 11 +60.5 + 21 +38.3749 + 0 +LINE + 8 +0 + 10 +60.5 + 20 +38.3749 + 11 +67.25 + 21 +35.9181 + 0 +LINE + 8 +0 + 10 +67.25 + 20 +35.9181 + 11 +68 + 21 +36.3511 + 0 +LINE + 8 +0 + 10 +68 + 20 +36.3511 + 11 +68 + 21 +46.3511 + 0 +LINE + 8 +0 + 10 +68 + 20 +46.3511 + 11 +67.25 + 21 +46.7841 + 0 +LINE + 8 +0 + 10 +67.25 + 20 +46.7841 + 11 +60.75 + 21 +44.4183 + 0 +LINE + 8 +0 + 10 +60.75 + 20 +44.4183 + 11 +60.75 + 21 +52.5623 + 0 +LINE + 8 +0 + 10 +60.75 + 20 +52.5623 + 11 +67.125 + 21 +50.2421 + 0 +LINE + 8 +0 + 10 +67.125 + 20 +50.2421 + 11 +68.25 + 21 +50.8916 + 0 +LINE + 8 +0 + 10 +68.25 + 20 +50.8916 + 11 +68.25 + 21 +60.8916 + 0 +LINE + 8 +0 + 10 +68.25 + 20 +60.8916 + 11 +67.125 + 21 +61.5411 + 0 +LINE + 8 +0 + 10 +67.125 + 20 +61.5411 + 11 +61 + 21 +59.3118 + 0 +LINE + 8 +0 + 10 +61 + 20 +59.3118 + 11 +61 + 21 +66.7498 + 0 +LINE + 8 +0 + 10 +61 + 20 +66.7498 + 11 +67 + 21 +64.566 + 0 +LINE + 8 +0 + 10 +67 + 20 +64.566 + 11 +68.5 + 21 +65.432 + 0 +LINE + 8 +0 + 10 +68.5 + 20 +65.432 + 11 +68.5 + 21 +75.432 + 0 +LINE + 8 +0 + 10 +68.5 + 20 +75.432 + 11 +67 + 21 +76.298 + 0 +LINE + 8 +0 + 10 +67 + 20 +76.298 + 11 +61.25 + 21 +74.2052 + 0 +LINE + 8 +0 + 10 +61.25 + 20 +74.2052 + 11 +61.25 + 21 +80.9372 + 0 +LINE + 8 +0 + 10 +61.25 + 20 +80.9372 + 11 +66.875 + 21 +78.8899 + 0 +LINE + 8 +0 + 10 +66.875 + 20 +78.8899 + 11 +68.75 + 21 +79.9724 + 0 +LINE + 8 +0 + 10 +68.75 + 20 +79.9724 + 11 +68.75 + 21 +89.9724 + 0 +LINE + 8 +0 + 10 +68.75 + 20 +89.9724 + 11 +66.875 + 21 +91.055 + 0 +LINE + 8 +0 + 10 +66.875 + 20 +91.055 + 11 +59.375 + 21 +88.3252 + 0 +LINE + 8 +0 + 10 +59.375 + 20 +88.3252 + 11 +51.875 + 21 +91.055 + 0 +LINE + 8 +0 + 10 +51.875 + 20 +91.055 + 11 +44.375 + 21 +88.3252 + 0 +LINE + 8 +0 + 10 +44.375 + 20 +88.3252 + 11 +36.875 + 21 +91.055 + 0 +LINE + 8 +0 + 10 +36.875 + 20 +91.055 + 11 +29.375 + 21 +88.3252 + 0 +LINE + 8 +0 + 10 +29.375 + 20 +88.3252 + 11 +21.875 + 21 +91.055 + 0 +LINE + 8 +0 + 10 +21.875 + 20 +91.055 + 11 +14.375 + 21 +88.3252 + 0 +LINE + 8 +0 + 10 +14.375 + 20 +88.3252 + 11 +6.87502 + 21 +91.055 + 0 +LINE + 8 +0 + 10 +6.87502 + 20 +91.055 + 11 +6.875 + 21 +81.055 + 0 +LINE + 8 +0 + 10 +6.875 + 20 +81.055 + 11 +-0.624985 + 21 +83.7847 + 0 +LINE + 8 +0 + 10 +-0.624985 + 20 +83.7847 + 11 +-0.625 + 21 +71.6197 + 0 +LINE + 8 +0 + 10 +-0.625 + 20 +71.6197 + 11 +6.87498 + 21 +74.3495 + 0 +LINE + 8 +0 + 10 +6.87498 + 20 +74.3495 + 11 +7.00002 + 21 +74.304 + 0 +LINE + 8 +0 + 10 +7.00002 + 20 +74.304 + 11 +7 + 21 +66.298 + 0 +LINE + 8 +0 + 10 +7 + 20 +66.298 + 11 +-0.499985 + 21 +69.0278 + 0 +LINE + 8 +0 + 10 +-0.499985 + 20 +69.0278 + 11 +-0.5 + 21 +57.2958 + 0 +LINE + 8 +0 + 10 +-0.5 + 20 +57.2958 + 11 +7 + 21 +60.0255 + 0 +LINE + 8 +0 + 10 +7 + 20 +60.0255 + 11 +7.12502 + 21 +59.98 + 0 +LINE + 8 +0 + 10 +7.12502 + 20 +59.98 + 11 +7.125 + 21 +51.5411 + 0 +LINE + 8 +0 + 10 +7.125 + 20 +51.5411 + 11 +-0.374985 + 21 +54.2708 + 0 +LINE + 8 +0 + 10 +-0.374985 + 20 +54.2708 + 11 +-0.375 + 21 +42.9718 + 0 +LINE + 8 +0 + 10 +-0.375 + 20 +42.9718 + 11 +7.125 + 21 +45.7016 + 0 +LINE + 8 +0 + 10 +7.125 + 20 +45.7016 + 11 +7.25002 + 21 +45.6561 + 0 +LINE + 8 +0 + 10 +7.25002 + 20 +45.6561 + 11 +7.25 + 21 +36.7841 + 0 +LINE + 8 +0 + 10 +7.25 + 20 +36.7841 + 11 +-0.249985 + 21 +39.5139 + 0 +LINE + 8 +0 + 10 +-0.249985 + 20 +39.5139 + 11 +-0.25 + 21 +28.6479 + 0 +LINE + 8 +0 + 10 +-0.25 + 20 +28.6479 + 11 +7.25 + 21 +31.3776 + 0 +LINE + 8 +0 + 10 +7.25 + 20 +31.3776 + 11 +7.37502 + 21 +31.3321 + 0 +LINE + 8 +0 + 10 +7.37502 + 20 +31.3321 + 11 +7.375 + 21 +22.0272 + 0 +LINE + 8 +0 + 10 +7.375 + 20 +22.0272 + 11 +-0.124985 + 21 +24.7569 + 0 +LINE + 8 +0 + 10 +-0.124985 + 20 +24.7569 + 11 +-0.125 + 21 +14.3239 + 0 +LINE + 8 +0 + 10 +-0.125 + 20 +14.3239 + 11 +7.375 + 21 +17.0537 + 0 +LINE + 8 +0 + 10 +7.375 + 20 +17.0537 + 11 +14.875 + 21 +14.3239 + 0 +LINE + 8 +0 + 10 +14.875 + 20 +14.3239 + 11 +22.375 + 21 +17.0537 + 0 +LINE + 8 +0 + 10 +22.375 + 20 +17.0537 + 11 +29.875 + 21 +14.3239 + 0 +LINE + 8 +0 + 10 +29.875 + 20 +14.3239 + 11 +37.375 + 21 +17.0537 + 0 +LINE + 8 +0 + 10 +37.375 + 20 +17.0537 + 11 +44.875 + 21 +14.3239 + 0 +LINE + 8 +0 + 10 +44.875 + 20 +14.3239 + 11 +52.375 + 21 +17.0537 + 0 +LINE + 8 +0 + 10 +52.375 + 20 +17.0537 + 11 +59.875 + 21 +14.3239 + 0 +LINE + 8 +0 + 10 +59.875 + 20 +14.3239 + 11 +60.25 + 21 +14.5404 + 0 +LINE + 8 +0 + 10 +44.375 + 20 +83.7847 + 11 +38.75 + 21 +81.7374 + 0 +LINE + 8 +0 + 10 +38.75 + 20 +81.7374 + 11 +38.75 + 21 +88.2075 + 0 +LINE + 8 +0 + 10 +38.75 + 20 +88.2075 + 11 +44.375 + 21 +86.1601 + 0 +LINE + 8 +0 + 10 +44.375 + 20 +86.1601 + 11 +51.875 + 21 +88.8899 + 0 +LINE + 8 +0 + 10 +51.875 + 20 +88.8899 + 11 +51.875 + 21 +81.055 + 0 +LINE + 8 +0 + 10 +51.875 + 20 +81.055 + 11 +44.375 + 21 +83.7847 + 0 +LINE + 8 +0 + 10 +59.375 + 20 +83.7847 + 11 +53.75 + 21 +81.7374 + 0 +LINE + 8 +0 + 10 +53.75 + 20 +81.7374 + 11 +53.75 + 21 +88.2075 + 0 +LINE + 8 +0 + 10 +53.75 + 20 +88.2075 + 11 +59.375 + 21 +86.1601 + 0 +LINE + 8 +0 + 10 +59.375 + 20 +86.1601 + 11 +66.875 + 21 +88.8899 + 0 +LINE + 8 +0 + 10 +66.875 + 20 +88.8899 + 11 +66.875 + 21 +81.055 + 0 +LINE + 8 +0 + 10 +66.875 + 20 +81.055 + 11 +59.375 + 21 +83.7847 + 0 +LINE + 8 +0 + 10 +29.375 + 20 +83.7847 + 11 +23.75 + 21 +81.7374 + 0 +LINE + 8 +0 + 10 +23.75 + 20 +81.7374 + 11 +23.75 + 21 +88.2075 + 0 +LINE + 8 +0 + 10 +23.75 + 20 +88.2075 + 11 +29.375 + 21 +86.1601 + 0 +LINE + 8 +0 + 10 +29.375 + 20 +86.1601 + 11 +36.875 + 21 +88.8899 + 0 +LINE + 8 +0 + 10 +36.875 + 20 +88.8899 + 11 +36.875 + 21 +81.055 + 0 +LINE + 8 +0 + 10 +36.875 + 20 +81.055 + 11 +29.375 + 21 +83.7847 + 0 +LINE + 8 +0 + 10 +14.375 + 20 +83.7847 + 11 +8.75 + 21 +81.7374 + 0 +LINE + 8 +0 + 10 +8.75 + 20 +81.7374 + 11 +8.75 + 21 +88.2075 + 0 +LINE + 8 +0 + 10 +8.75 + 20 +88.2075 + 11 +14.375 + 21 +86.1601 + 0 +LINE + 8 +0 + 10 +14.375 + 20 +86.1601 + 11 +21.875 + 21 +88.8899 + 0 +LINE + 8 +0 + 10 +21.875 + 20 +88.8899 + 11 +21.875 + 21 +81.055 + 0 +LINE + 8 +0 + 10 +21.875 + 20 +81.055 + 11 +14.375 + 21 +83.7847 + 0 +LINE + 8 +0 + 10 +6.875 + 20 +76.5145 + 11 +1.25 + 21 +74.4672 + 0 +LINE + 8 +0 + 10 +1.25 + 20 +74.4672 + 11 +1.25 + 21 +80.9372 + 0 +LINE + 8 +0 + 10 +1.25 + 20 +80.9372 + 11 +6.87498 + 21 +78.8899 + 0 +LINE + 8 +0 + 10 +6.87498 + 20 +78.8899 + 11 +14.375 + 21 +81.6197 + 0 +LINE + 8 +0 + 10 +14.375 + 20 +81.6197 + 11 +14.375 + 21 +73.7847 + 0 +LINE + 8 +0 + 10 +14.375 + 20 +73.7847 + 11 +6.875 + 21 +76.5145 + 0 +LINE + 8 +0 + 10 +21.875 + 20 +76.5145 + 11 +16.25 + 21 +74.4672 + 0 +LINE + 8 +0 + 10 +16.25 + 20 +74.4672 + 11 +16.25 + 21 +80.9372 + 0 +LINE + 8 +0 + 10 +16.25 + 20 +80.9372 + 11 +21.875 + 21 +78.8899 + 0 +LINE + 8 +0 + 10 +21.875 + 20 +78.8899 + 11 +29.375 + 21 +81.6197 + 0 +LINE + 8 +0 + 10 +29.375 + 20 +81.6197 + 11 +29.375 + 21 +73.7847 + 0 +LINE + 8 +0 + 10 +29.375 + 20 +73.7847 + 11 +21.875 + 21 +76.5145 + 0 +LINE + 8 +0 + 10 +36.875 + 20 +76.5145 + 11 +31.25 + 21 +74.4672 + 0 +LINE + 8 +0 + 10 +31.25 + 20 +74.4672 + 11 +31.25 + 21 +80.9372 + 0 +LINE + 8 +0 + 10 +31.25 + 20 +80.9372 + 11 +36.875 + 21 +78.8899 + 0 +LINE + 8 +0 + 10 +36.875 + 20 +78.8899 + 11 +44.375 + 21 +81.6197 + 0 +LINE + 8 +0 + 10 +44.375 + 20 +81.6197 + 11 +44.375 + 21 +73.7847 + 0 +LINE + 8 +0 + 10 +44.375 + 20 +73.7847 + 11 +36.875 + 21 +76.5145 + 0 +LINE + 8 +0 + 10 +51.875 + 20 +76.5145 + 11 +46.25 + 21 +74.4672 + 0 +LINE + 8 +0 + 10 +46.25 + 20 +74.4672 + 11 +46.25 + 21 +80.9372 + 0 +LINE + 8 +0 + 10 +46.25 + 20 +80.9372 + 11 +51.875 + 21 +78.8899 + 0 +LINE + 8 +0 + 10 +51.875 + 20 +78.8899 + 11 +59.375 + 21 +81.6197 + 0 +LINE + 8 +0 + 10 +59.375 + 20 +81.6197 + 11 +59.375 + 21 +73.7847 + 0 +LINE + 8 +0 + 10 +59.375 + 20 +73.7847 + 11 +51.875 + 21 +76.5145 + 0 +LINE + 8 +0 + 10 +59.5 + 20 +69.0278 + 11 +53.5 + 21 +66.844 + 0 +LINE + 8 +0 + 10 +53.5 + 20 +66.844 + 11 +53.5 + 21 +73.758 + 0 +LINE + 8 +0 + 10 +53.5 + 20 +73.758 + 11 +59.375 + 21 +71.6197 + 0 +LINE + 8 +0 + 10 +59.375 + 20 +71.6197 + 11 +60.1763 + 21 +72.0823 + 0 +LINE + 8 +0 + 10 +60.1763 + 20 +72.0823 + 11 +67 + 21 +74.566 + 0 +LINE + 8 +0 + 10 +67 + 20 +74.566 + 11 +67 + 21 +66.298 + 0 +LINE + 8 +0 + 10 +67 + 20 +66.298 + 11 +59.5 + 21 +69.0278 + 0 +LINE + 8 +0 + 10 +44.5 + 20 +69.0278 + 11 +38.5 + 21 +66.844 + 0 +LINE + 8 +0 + 10 +38.5 + 20 +66.844 + 11 +38.5 + 21 +73.758 + 0 +LINE + 8 +0 + 10 +38.5 + 20 +73.758 + 11 +44.375 + 21 +71.6197 + 0 +LINE + 8 +0 + 10 +44.375 + 20 +71.6197 + 11 +51.875 + 21 +74.3495 + 0 +LINE + 8 +0 + 10 +51.875 + 20 +74.3495 + 11 +52 + 21 +74.304 + 0 +LINE + 8 +0 + 10 +52 + 20 +74.304 + 11 +52 + 21 +66.298 + 0 +LINE + 8 +0 + 10 +52 + 20 +66.298 + 11 +44.5 + 21 +69.0278 + 0 +LINE + 8 +0 + 10 +29.5 + 20 +69.0278 + 11 +23.5 + 21 +66.844 + 0 +LINE + 8 +0 + 10 +23.5 + 20 +66.844 + 11 +23.5 + 21 +73.758 + 0 +LINE + 8 +0 + 10 +23.5 + 20 +73.758 + 11 +29.375 + 21 +71.6197 + 0 +LINE + 8 +0 + 10 +29.375 + 20 +71.6197 + 11 +36.875 + 21 +74.3495 + 0 +LINE + 8 +0 + 10 +36.875 + 20 +74.3495 + 11 +37 + 21 +74.304 + 0 +LINE + 8 +0 + 10 +37 + 20 +74.304 + 11 +37 + 21 +66.298 + 0 +LINE + 8 +0 + 10 +37 + 20 +66.298 + 11 +29.5 + 21 +69.0278 + 0 +LINE + 8 +0 + 10 +14.5 + 20 +69.0278 + 11 +8.5 + 21 +66.844 + 0 +LINE + 8 +0 + 10 +8.5 + 20 +66.844 + 11 +8.5 + 21 +73.758 + 0 +LINE + 8 +0 + 10 +8.5 + 20 +73.758 + 11 +14.375 + 21 +71.6197 + 0 +LINE + 8 +0 + 10 +14.375 + 20 +71.6197 + 11 +21.875 + 21 +74.3495 + 0 +LINE + 8 +0 + 10 +21.875 + 20 +74.3495 + 11 +22 + 21 +74.304 + 0 +LINE + 8 +0 + 10 +22 + 20 +74.304 + 11 +22 + 21 +66.298 + 0 +LINE + 8 +0 + 10 +22 + 20 +66.298 + 11 +14.5 + 21 +69.0278 + 0 +LINE + 8 +0 + 10 +37 + 20 +61.7576 + 11 +31 + 21 +59.5737 + 0 +LINE + 8 +0 + 10 +31 + 20 +59.5737 + 11 +31 + 21 +66.7498 + 0 +LINE + 8 +0 + 10 +31 + 20 +66.7498 + 11 +37 + 21 +64.566 + 0 +LINE + 8 +0 + 10 +37 + 20 +64.566 + 11 +44.5 + 21 +67.2957 + 0 +LINE + 8 +0 + 10 +44.5 + 20 +67.2957 + 11 +44.5 + 21 +59.0278 + 0 +LINE + 8 +0 + 10 +44.5 + 20 +59.0278 + 11 +37 + 21 +61.7576 + 0 +LINE + 8 +0 + 10 +52 + 20 +61.7576 + 11 +46 + 21 +59.5737 + 0 +LINE + 8 +0 + 10 +46 + 20 +59.5737 + 11 +46 + 21 +66.7498 + 0 +LINE + 8 +0 + 10 +46 + 20 +66.7498 + 11 +52 + 21 +64.566 + 0 +LINE + 8 +0 + 10 +52 + 20 +64.566 + 11 +59.5 + 21 +67.2957 + 0 +LINE + 8 +0 + 10 +59.5 + 20 +67.2957 + 11 +59.5 + 21 +59.0278 + 0 +LINE + 8 +0 + 10 +59.5 + 20 +59.0278 + 11 +52 + 21 +61.7576 + 0 +LINE + 8 +0 + 10 +22 + 20 +61.7576 + 11 +16 + 21 +59.5737 + 0 +LINE + 8 +0 + 10 +16 + 20 +59.5737 + 11 +16 + 21 +66.7498 + 0 +LINE + 8 +0 + 10 +16 + 20 +66.7498 + 11 +22 + 21 +64.566 + 0 +LINE + 8 +0 + 10 +22 + 20 +64.566 + 11 +29.5 + 21 +67.2957 + 0 +LINE + 8 +0 + 10 +29.5 + 20 +67.2957 + 11 +29.5 + 21 +59.0278 + 0 +LINE + 8 +0 + 10 +29.5 + 20 +59.0278 + 11 +22 + 21 +61.7576 + 0 +LINE + 8 +0 + 10 +7 + 20 +61.7576 + 11 +1 + 21 +59.5737 + 0 +LINE + 8 +0 + 10 +1 + 20 +59.5737 + 11 +1 + 21 +66.7498 + 0 +LINE + 8 +0 + 10 +1 + 20 +66.7498 + 11 +7 + 21 +64.566 + 0 +LINE + 8 +0 + 10 +7 + 20 +64.566 + 11 +14.5 + 21 +67.2957 + 0 +LINE + 8 +0 + 10 +14.5 + 20 +67.2957 + 11 +14.5 + 21 +59.0278 + 0 +LINE + 8 +0 + 10 +14.5 + 20 +59.0278 + 11 +7 + 21 +61.7576 + 0 +LINE + 8 +0 + 10 +59.625 + 20 +54.2708 + 11 +53.25 + 21 +51.9505 + 0 +LINE + 8 +0 + 10 +53.25 + 20 +51.9505 + 11 +53.25 + 21 +59.5706 + 0 +LINE + 8 +0 + 10 +53.25 + 20 +59.5706 + 11 +59.5 + 21 +57.2958 + 0 +LINE + 8 +0 + 10 +59.5 + 20 +57.2958 + 11 +60.3014 + 21 +57.7584 + 0 +LINE + 8 +0 + 10 +60.3014 + 20 +57.7584 + 11 +67.125 + 21 +60.242 + 0 +LINE + 8 +0 + 10 +67.125 + 20 +60.242 + 11 +67.125 + 21 +51.5411 + 0 +LINE + 8 +0 + 10 +67.125 + 20 +51.5411 + 11 +59.625 + 21 +54.2708 + 0 +LINE + 8 +0 + 10 +44.625 + 20 +54.2708 + 11 +38.25 + 21 +51.9505 + 0 +LINE + 8 +0 + 10 +38.25 + 20 +51.9505 + 11 +38.25 + 21 +59.5706 + 0 +LINE + 8 +0 + 10 +38.25 + 20 +59.5706 + 11 +44.5 + 21 +57.2958 + 0 +LINE + 8 +0 + 10 +44.5 + 20 +57.2958 + 11 +52 + 21 +60.0255 + 0 +LINE + 8 +0 + 10 +52 + 20 +60.0255 + 11 +52.125 + 21 +59.98 + 0 +LINE + 8 +0 + 10 +52.125 + 20 +59.98 + 11 +52.125 + 21 +51.5411 + 0 +LINE + 8 +0 + 10 +52.125 + 20 +51.5411 + 11 +44.625 + 21 +54.2708 + 0 +LINE + 8 +0 + 10 +29.625 + 20 +54.2708 + 11 +23.25 + 21 +51.9505 + 0 +LINE + 8 +0 + 10 +23.25 + 20 +51.9505 + 11 +23.25 + 21 +59.5706 + 0 +LINE + 8 +0 + 10 +23.25 + 20 +59.5706 + 11 +29.5 + 21 +57.2958 + 0 +LINE + 8 +0 + 10 +29.5 + 20 +57.2958 + 11 +37 + 21 +60.0255 + 0 +LINE + 8 +0 + 10 +37 + 20 +60.0255 + 11 +37.125 + 21 +59.98 + 0 +LINE + 8 +0 + 10 +37.125 + 20 +59.98 + 11 +37.125 + 21 +51.5411 + 0 +LINE + 8 +0 + 10 +37.125 + 20 +51.5411 + 11 +29.625 + 21 +54.2708 + 0 +LINE + 8 +0 + 10 +14.625 + 20 +54.2708 + 11 +8.25 + 21 +51.9505 + 0 +LINE + 8 +0 + 10 +8.25 + 20 +51.9505 + 11 +8.25 + 21 +59.5706 + 0 +LINE + 8 +0 + 10 +8.25 + 20 +59.5706 + 11 +14.5 + 21 +57.2958 + 0 +LINE + 8 +0 + 10 +14.5 + 20 +57.2958 + 11 +22 + 21 +60.0255 + 0 +LINE + 8 +0 + 10 +22 + 20 +60.0255 + 11 +22.125 + 21 +59.98 + 0 +LINE + 8 +0 + 10 +22.125 + 20 +59.98 + 11 +22.125 + 21 +51.5411 + 0 +LINE + 8 +0 + 10 +22.125 + 20 +51.5411 + 11 +14.625 + 21 +54.2708 + 0 +LINE + 8 +0 + 10 +52.125 + 20 +47.0006 + 11 +45.75 + 21 +44.6803 + 0 +LINE + 8 +0 + 10 +45.75 + 20 +44.6803 + 11 +45.75 + 21 +52.5623 + 0 +LINE + 8 +0 + 10 +45.75 + 20 +52.5623 + 11 +52.125 + 21 +50.242 + 0 +LINE + 8 +0 + 10 +52.125 + 20 +50.242 + 11 +59.625 + 21 +52.9718 + 0 +LINE + 8 +0 + 10 +59.625 + 20 +52.9718 + 11 +59.625 + 21 +44.2708 + 0 +LINE + 8 +0 + 10 +59.625 + 20 +44.2708 + 11 +52.125 + 21 +47.0006 + 0 +LINE + 8 +0 + 10 +37.125 + 20 +47.0006 + 11 +30.75 + 21 +44.6803 + 0 +LINE + 8 +0 + 10 +30.75 + 20 +44.6803 + 11 +30.75 + 21 +52.5623 + 0 +LINE + 8 +0 + 10 +30.75 + 20 +52.5623 + 11 +37.125 + 21 +50.242 + 0 +LINE + 8 +0 + 10 +37.125 + 20 +50.242 + 11 +44.625 + 21 +52.9718 + 0 +LINE + 8 +0 + 10 +44.625 + 20 +52.9718 + 11 +44.625 + 21 +44.2708 + 0 +LINE + 8 +0 + 10 +44.625 + 20 +44.2708 + 11 +37.125 + 21 +47.0006 + 0 +LINE + 8 +0 + 10 +22.125 + 20 +47.0006 + 11 +15.75 + 21 +44.6803 + 0 +LINE + 8 +0 + 10 +15.75 + 20 +44.6803 + 11 +15.75 + 21 +52.5623 + 0 +LINE + 8 +0 + 10 +15.75 + 20 +52.5623 + 11 +22.125 + 21 +50.242 + 0 +LINE + 8 +0 + 10 +22.125 + 20 +50.242 + 11 +29.625 + 21 +52.9718 + 0 +LINE + 8 +0 + 10 +29.625 + 20 +52.9718 + 11 +29.625 + 21 +44.2708 + 0 +LINE + 8 +0 + 10 +29.625 + 20 +44.2708 + 11 +22.125 + 21 +47.0006 + 0 +LINE + 8 +0 + 10 +7.125 + 20 +47.0006 + 11 +0.75 + 21 +44.6803 + 0 +LINE + 8 +0 + 10 +0.75 + 20 +44.6803 + 11 +0.75 + 21 +52.5623 + 0 +LINE + 8 +0 + 10 +0.75 + 20 +52.5623 + 11 +7.125 + 21 +50.242 + 0 +LINE + 8 +0 + 10 +7.125 + 20 +50.242 + 11 +14.625 + 21 +52.9718 + 0 +LINE + 8 +0 + 10 +14.625 + 20 +52.9718 + 11 +14.625 + 21 +44.2708 + 0 +LINE + 8 +0 + 10 +14.625 + 20 +44.2708 + 11 +7.125 + 21 +47.0006 + 0 +LINE + 8 +0 + 10 +59.75 + 20 +39.5139 + 11 +53 + 21 +37.0571 + 0 +LINE + 8 +0 + 10 +53 + 20 +37.0571 + 11 +53 + 21 +45.3831 + 0 +LINE + 8 +0 + 10 +53 + 20 +45.3831 + 11 +59.625 + 21 +42.9718 + 0 +LINE + 8 +0 + 10 +59.625 + 20 +42.9718 + 11 +60.4263 + 21 +43.4345 + 0 +LINE + 8 +0 + 10 +60.4263 + 20 +43.4345 + 11 +67.25 + 21 +45.9181 + 0 +LINE + 8 +0 + 10 +67.25 + 20 +45.9181 + 11 +67.25 + 21 +36.7841 + 0 +LINE + 8 +0 + 10 +67.25 + 20 +36.7841 + 11 +59.75 + 21 +39.5139 + 0 +LINE + 8 +0 + 10 +44.75 + 20 +39.5139 + 11 +38 + 21 +37.0571 + 0 +LINE + 8 +0 + 10 +38 + 20 +37.0571 + 11 +38 + 21 +45.3831 + 0 +LINE + 8 +0 + 10 +38 + 20 +45.3831 + 11 +44.625 + 21 +42.9718 + 0 +LINE + 8 +0 + 10 +44.625 + 20 +42.9718 + 11 +52.125 + 21 +45.7016 + 0 +LINE + 8 +0 + 10 +52.125 + 20 +45.7016 + 11 +52.25 + 21 +45.6561 + 0 +LINE + 8 +0 + 10 +52.25 + 20 +45.6561 + 11 +52.25 + 21 +36.7841 + 0 +LINE + 8 +0 + 10 +52.25 + 20 +36.7841 + 11 +44.75 + 21 +39.5139 + 0 +LINE + 8 +0 + 10 +29.75 + 20 +39.5139 + 11 +23 + 21 +37.0571 + 0 +LINE + 8 +0 + 10 +23 + 20 +37.0571 + 11 +23 + 21 +45.3831 + 0 +LINE + 8 +0 + 10 +23 + 20 +45.3831 + 11 +29.625 + 21 +42.9718 + 0 +LINE + 8 +0 + 10 +29.625 + 20 +42.9718 + 11 +37.125 + 21 +45.7016 + 0 +LINE + 8 +0 + 10 +37.125 + 20 +45.7016 + 11 +37.25 + 21 +45.6561 + 0 +LINE + 8 +0 + 10 +37.25 + 20 +45.6561 + 11 +37.25 + 21 +36.7841 + 0 +LINE + 8 +0 + 10 +37.25 + 20 +36.7841 + 11 +29.75 + 21 +39.5139 + 0 +LINE + 8 +0 + 10 +14.75 + 20 +39.5139 + 11 +8 + 21 +37.0571 + 0 +LINE + 8 +0 + 10 +8 + 20 +37.0571 + 11 +8 + 21 +45.3831 + 0 +LINE + 8 +0 + 10 +8 + 20 +45.3831 + 11 +14.625 + 21 +42.9718 + 0 +LINE + 8 +0 + 10 +14.625 + 20 +42.9718 + 11 +22.125 + 21 +45.7016 + 0 +LINE + 8 +0 + 10 +22.125 + 20 +45.7016 + 11 +22.25 + 21 +45.6561 + 0 +LINE + 8 +0 + 10 +22.25 + 20 +45.6561 + 11 +22.25 + 21 +36.7841 + 0 +LINE + 8 +0 + 10 +22.25 + 20 +36.7841 + 11 +14.75 + 21 +39.5139 + 0 +LINE + 8 +0 + 10 +22.25 + 20 +32.2437 + 11 +15.5 + 21 +29.7869 + 0 +LINE + 8 +0 + 10 +15.5 + 20 +29.7869 + 11 +15.5 + 21 +38.3749 + 0 +LINE + 8 +0 + 10 +15.5 + 20 +38.3749 + 11 +22.25 + 21 +35.9181 + 0 +LINE + 8 +0 + 10 +22.25 + 20 +35.9181 + 11 +29.75 + 21 +38.6479 + 0 +LINE + 8 +0 + 10 +29.75 + 20 +38.6479 + 11 +29.75 + 21 +29.5139 + 0 +LINE + 8 +0 + 10 +29.75 + 20 +29.5139 + 11 +22.25 + 21 +32.2437 + 0 +LINE + 8 +0 + 10 +37.25 + 20 +32.2437 + 11 +30.5 + 21 +29.7869 + 0 +LINE + 8 +0 + 10 +30.5 + 20 +29.7869 + 11 +30.5 + 21 +38.3749 + 0 +LINE + 8 +0 + 10 +30.5 + 20 +38.3749 + 11 +37.25 + 21 +35.9181 + 0 +LINE + 8 +0 + 10 +37.25 + 20 +35.9181 + 11 +44.75 + 21 +38.6479 + 0 +LINE + 8 +0 + 10 +44.75 + 20 +38.6479 + 11 +44.75 + 21 +29.5139 + 0 +LINE + 8 +0 + 10 +44.75 + 20 +29.5139 + 11 +37.25 + 21 +32.2437 + 0 +LINE + 8 +0 + 10 +52.25 + 20 +32.2437 + 11 +45.5 + 21 +29.7869 + 0 +LINE + 8 +0 + 10 +45.5 + 20 +29.7869 + 11 +45.5 + 21 +38.3749 + 0 +LINE + 8 +0 + 10 +45.5 + 20 +38.3749 + 11 +52.25 + 21 +35.9181 + 0 +LINE + 8 +0 + 10 +52.25 + 20 +35.9181 + 11 +59.75 + 21 +38.6479 + 0 +LINE + 8 +0 + 10 +59.75 + 20 +38.6479 + 11 +59.75 + 21 +29.5139 + 0 +LINE + 8 +0 + 10 +59.75 + 20 +29.5139 + 11 +52.25 + 21 +32.2437 + 0 +LINE + 8 +0 + 10 +7.25 + 20 +32.2437 + 11 +0.5 + 21 +29.7869 + 0 +LINE + 8 +0 + 10 +0.5 + 20 +29.7869 + 11 +0.5 + 21 +38.3749 + 0 +LINE + 8 +0 + 10 +0.5 + 20 +38.3749 + 11 +7.25 + 21 +35.9181 + 0 +LINE + 8 +0 + 10 +7.25 + 20 +35.9181 + 11 +14.75 + 21 +38.6479 + 0 +LINE + 8 +0 + 10 +14.75 + 20 +38.6479 + 11 +14.75 + 21 +29.5139 + 0 +LINE + 8 +0 + 10 +14.75 + 20 +29.5139 + 11 +7.25 + 21 +32.2437 + 0 +LINE + 8 +0 + 10 +59.875 + 20 +24.7569 + 11 +52.75 + 21 +22.1637 + 0 +LINE + 8 +0 + 10 +52.75 + 20 +22.1637 + 11 +52.75 + 21 +31.1957 + 0 +LINE + 8 +0 + 10 +52.75 + 20 +31.1957 + 11 +59.75 + 21 +28.6479 + 0 +LINE + 8 +0 + 10 +59.75 + 20 +28.6479 + 11 +60.5 + 21 +29.0809 + 0 +LINE + 8 +0 + 10 +60.5 + 20 +29.0809 + 11 +60.5 + 21 +29.0918 + 0 +LINE + 8 +0 + 10 +60.5 + 20 +29.0918 + 11 +67.375 + 21 +31.5941 + 0 +LINE + 8 +0 + 10 +67.375 + 20 +31.5941 + 11 +67.375 + 21 +22.0271 + 0 +LINE + 8 +0 + 10 +67.375 + 20 +22.0271 + 11 +59.875 + 21 +24.7569 + 0 +LINE + 8 +0 + 10 +44.875 + 20 +24.7569 + 11 +37.75 + 21 +22.1637 + 0 +LINE + 8 +0 + 10 +37.75 + 20 +22.1637 + 11 +37.75 + 21 +31.1957 + 0 +LINE + 8 +0 + 10 +37.75 + 20 +31.1957 + 11 +44.75 + 21 +28.6479 + 0 +LINE + 8 +0 + 10 +44.75 + 20 +28.6479 + 11 +52.25 + 21 +31.3776 + 0 +LINE + 8 +0 + 10 +52.25 + 20 +31.3776 + 11 +52.375 + 21 +31.3321 + 0 +LINE + 8 +0 + 10 +52.375 + 20 +31.3321 + 11 +52.375 + 21 +22.0272 + 0 +LINE + 8 +0 + 10 +52.375 + 20 +22.0272 + 11 +44.875 + 21 +24.7569 + 0 +LINE + 8 +0 + 10 +29.875 + 20 +24.7569 + 11 +22.75 + 21 +22.1637 + 0 +LINE + 8 +0 + 10 +22.75 + 20 +22.1637 + 11 +22.75 + 21 +31.1957 + 0 +LINE + 8 +0 + 10 +22.75 + 20 +31.1957 + 11 +29.75 + 21 +28.6479 + 0 +LINE + 8 +0 + 10 +29.75 + 20 +28.6479 + 11 +37.25 + 21 +31.3776 + 0 +LINE + 8 +0 + 10 +37.25 + 20 +31.3776 + 11 +37.375 + 21 +31.3321 + 0 +LINE + 8 +0 + 10 +37.375 + 20 +31.3321 + 11 +37.375 + 21 +22.0272 + 0 +LINE + 8 +0 + 10 +37.375 + 20 +22.0272 + 11 +29.875 + 21 +24.7569 + 0 +LINE + 8 +0 + 10 +14.875 + 20 +24.7569 + 11 +7.75 + 21 +22.1637 + 0 +LINE + 8 +0 + 10 +7.75 + 20 +22.1637 + 11 +7.75 + 21 +31.1957 + 0 +LINE + 8 +0 + 10 +7.75 + 20 +31.1957 + 11 +14.75 + 21 +28.6479 + 0 +LINE + 8 +0 + 10 +14.75 + 20 +28.6479 + 11 +22.25 + 21 +31.3776 + 0 +LINE + 8 +0 + 10 +22.25 + 20 +31.3776 + 11 +22.375 + 21 +31.3321 + 0 +LINE + 8 +0 + 10 +22.375 + 20 +31.3321 + 11 +22.375 + 21 +22.0272 + 0 +LINE + 8 +0 + 10 +22.375 + 20 +22.0272 + 11 +14.875 + 21 +24.7569 + 0 +LINE + 8 +0 + 10 +7.375 + 20 +17.4867 + 11 +0.25 + 21 +14.8934 + 0 +LINE + 8 +0 + 10 +0.25 + 20 +14.8934 + 11 +0.25 + 21 +24.1874 + 0 +LINE + 8 +0 + 10 +0.25 + 20 +24.1874 + 11 +7.375 + 21 +21.5941 + 0 +LINE + 8 +0 + 10 +7.375 + 20 +21.5941 + 11 +14.875 + 21 +24.3239 + 0 +LINE + 8 +0 + 10 +14.875 + 20 +24.3239 + 11 +14.875 + 21 +14.7569 + 0 +LINE + 8 +0 + 10 +14.875 + 20 +14.7569 + 11 +7.375 + 21 +17.4867 + 0 +LINE + 8 +0 + 10 +37.375 + 20 +17.4867 + 11 +30.25 + 21 +14.8934 + 0 +LINE + 8 +0 + 10 +30.25 + 20 +14.8934 + 11 +30.25 + 21 +24.1874 + 0 +LINE + 8 +0 + 10 +30.25 + 20 +24.1874 + 11 +37.375 + 21 +21.5941 + 0 +LINE + 8 +0 + 10 +37.375 + 20 +21.5941 + 11 +44.875 + 21 +24.3239 + 0 +LINE + 8 +0 + 10 +44.875 + 20 +24.3239 + 11 +44.875 + 21 +14.7569 + 0 +LINE + 8 +0 + 10 +44.875 + 20 +14.7569 + 11 +37.375 + 21 +17.4867 + 0 +LINE + 8 +0 + 10 +22.375 + 20 +17.4867 + 11 +15.25 + 21 +14.8934 + 0 +LINE + 8 +0 + 10 +15.25 + 20 +14.8934 + 11 +15.25 + 21 +24.1874 + 0 +LINE + 8 +0 + 10 +15.25 + 20 +24.1874 + 11 +22.375 + 21 +21.5941 + 0 +LINE + 8 +0 + 10 +22.375 + 20 +21.5941 + 11 +29.875 + 21 +24.3239 + 0 +LINE + 8 +0 + 10 +29.875 + 20 +24.3239 + 11 +29.875 + 21 +14.7569 + 0 +LINE + 8 +0 + 10 +29.875 + 20 +14.7569 + 11 +22.375 + 21 +17.4867 + 0 +LINE + 8 +0 + 10 +52.375 + 20 +17.4867 + 11 +45.25 + 21 +14.8934 + 0 +LINE + 8 +0 + 10 +45.25 + 20 +14.8934 + 11 +45.25 + 21 +24.1874 + 0 +LINE + 8 +0 + 10 +45.25 + 20 +24.1874 + 11 +52.375 + 21 +21.5941 + 0 +LINE + 8 +0 + 10 +52.375 + 20 +21.5941 + 11 +59.875 + 21 +24.3239 + 0 +LINE + 8 +0 + 10 +59.875 + 20 +24.3239 + 11 +59.875 + 21 +14.7569 + 0 +LINE + 8 +0 + 10 +59.875 + 20 +14.7569 + 11 +52.375 + 21 +17.4867 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 0 +ENDSEC + 0 +EOF diff --git a/examples/import-dxf-2.py b/examples/import-dxf-2.py new file mode 100644 index 0000000..630fe23 --- /dev/null +++ b/examples/import-dxf-2.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +import os +import sys + +import pycalculix as pyc + +model_name = 'import-dxf-2' +model = pyc.FeaModel(model_name) +model.set_units('m') + +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + +abs_path = os.path.dirname(os.path.abspath(__file__)) +fname = os.path.join(abs_path, '%s.dxf' % model_name) +importer = pyc.CadImporter(model, fname, swapxy=True) +parts = importer.load() +model.plot_geometry(model_name+'_imported', display=show_gui) +#parts[0].chunk() +model.plot_geometry(model_name+'_areas', pnum=False, + lnum=False, display=show_gui) +model.plot_geometry(model_name+'_lines', anum=False, + pnum=False, display=show_gui) +model.plot_geometry(model_name+'_points', anum=False, + lnum=False, display=show_gui) + + +model.view.print_summary() + +model.set_etype('axisym', parts) +model.set_eshape(eshape, 2) +model.mesh(1.0, 'gmsh') +model.plot_elements(model_name+'_elements', display=show_gui) +model.view.print_summary() diff --git a/examples/import-dxf.py b/examples/import-dxf.py deleted file mode 100644 index a5a5b59..0000000 --- a/examples/import-dxf.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -import pycalculix as pyc - -model_name = 'import-dxf' -model = pyc.FeaModel(model_name) -model.set_units('m') - -#fname = 'test.dxf' -fname = 'kontrola.dxf' -importer = pyc.CadImporter(model, fname, swapxy=True) -parts = importer.load() -model.plot_geometry(model_name+'_imported') -#parts[0].chunk() -model.plot_geometry(model_name+'_chunked_areas', pnum=False, lnum=False) -model.plot_geometry(model_name+'_chunked_lines', anum=False, pnum=False) -model.plot_geometry(model_name+'_chunked_points', anum=False, lnum=False) - -model.view.print_summary() - -model.set_etype('axisym', parts) -model.set_eshape('quad', 2) -model.mesh(1.0, 'gmsh') -model.plot_elements(model_name+'_elements') -model.view.print_summary() diff --git a/examples/kontrola.pdf b/examples/kontrola.pdf deleted file mode 100644 index ccf431b..0000000 Binary files a/examples/kontrola.pdf and /dev/null differ diff --git a/examples/multihole.py b/examples/multihole.py index bf33d2e..9aa4d45 100644 --- a/examples/multihole.py +++ b/examples/multihole.py @@ -1,10 +1,21 @@ #!/usr/bin/env python3 +import sys + import pycalculix as pyc model_name = 'multihole' model = pyc.FeaModel(model_name) model.set_units('m') +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + width = 5 length = 8 radius = 0.5 @@ -45,16 +56,20 @@ part.draw_line_ax(-hole_width) -model.plot_geometry(model_name+'_prechunk_areas', lnum=False, pnum=False) -model.plot_geometry(model_name+'_prechunk_lines', pnum=False) -model.plot_geometry(model_name+'_prechunk_points', lnum=False) +model.plot_geometry(model_name+'_prechunk_areas', lnum=False, + pnum=False, display=show_gui) +model.plot_geometry(model_name+'_prechunk_lines', pnum=False, + display=show_gui) +model.plot_geometry(model_name+'_prechunk_points', lnum=False, + display=show_gui) part.chunk(debug=[0,0]) -model.plot_geometry(model_name+'_chunked_areas', lnum=False, pnum=False) +model.plot_geometry(model_name+'_chunked_areas', lnum=False, + pnum=False, display=show_gui) -model.set_eshape('quad', 2) +model.set_eshape(eshape, 2) model.set_etype('plstrain', part, 0.1) model.mesh(0.7, 'gmsh') -model.plot_elements() +model.plot_elements(display=show_gui) model.view.print_summary() diff --git a/examples/pinned-plate.py b/examples/pinned-plate.py index e3f5b3b..a552e9b 100644 --- a/examples/pinned-plate.py +++ b/examples/pinned-plate.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +import sys + import pycalculix as pyc # Model of a pinned plate with 3 pins @@ -7,6 +9,15 @@ model = pyc.FeaModel(proj_name) model.set_units('in') +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + # pin locations pin1 = [0, 0] pin2 = [pin1[0], 4] @@ -61,12 +72,13 @@ model.set_ediv(all_arcs, 10) # plot model -model.plot_areas(proj_name+'_prechunk_areas', label=False) +model.plot_areas(proj_name+'_prechunk_areas', label=False, + display=show_gui) part.chunk('ext') -model.plot_areas(proj_name+'_areas', label=False) -model.plot_parts(proj_name+'_parts') -model.plot_points(proj_name+'_points') -model.plot_lines(proj_name+'_points', label=False) +model.plot_areas(proj_name+'_areas', label=False, display=show_gui) +model.plot_parts(proj_name+'_parts', display=show_gui) +model.plot_points(proj_name+'_points', display=show_gui) +model.plot_lines(proj_name+'_points', label=False, display=show_gui) # set loads and constraints pin_vert_pts = model.get_items(['P40','P42', 'P45', 'P47']) @@ -94,19 +106,19 @@ model.set_contact_linear(pin, hole, kval, True) # mesh the model -model.set_eshape('tri', 2) +model.set_eshape(eshape, 2) model.set_etype('plstress', part, 0.1) model.set_etype('plstress', pin_parts, 0.1) model.mesh(0.5, 'gmsh') -model.plot_elements(proj_name+'_elem') # plot part elements -model.plot_constraints(proj_name+'_constr') +model.plot_elements(proj_name+'_elem', display=show_gui) +model.plot_constraints(proj_name+'_constr', display=show_gui) # make and solve the model prob = pyc.Problem(model, 'struct') prob.solve() # Plot results -fields = 'Sx,Sy,S1,S2,S3,Seqv,ux,uy,utot' # store the fields to plot +fields = 'Sx,Sy,S1,S2,S3,Seqv,ux,uy,utot' # store the fields to plot fields = fields.split(',') for field in fields: fname = proj_name+'_'+field diff --git a/examples/pipe-crush-elastic.py b/examples/pipe-crush-elastic.py index 271648a..e2cf754 100644 --- a/examples/pipe-crush-elastic.py +++ b/examples/pipe-crush-elastic.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +import sys + import pycalculix as pyc # Model of a pipe being crushed, quarter-symmetrym plane strain @@ -6,6 +8,15 @@ model = pyc.FeaModel(proj_name) model.set_units('m') # this sets dist units to meters +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + # Define variables we'll use to draw part geometry rad_outer = 0.1143 # pipe outer radius pipe_wall_th = .00887 # wall thickness @@ -13,7 +24,6 @@ pipe_length = 10*rad_outer wall_elements = 4.0 e_size = pipe_wall_th/wall_elements -disp = False # Draw pipe, x, y = radial, axial pipe = pyc.Part(model) @@ -36,10 +46,10 @@ num_plate_eles = int(round(plate_bottom.length()/e_size,0)) # view model -model.plot_geometry(proj_name+'_geometry', display=disp) -model.plot_parts(proj_name+'_parts', display=disp) -model.plot_areas(proj_name+'_areas', display=disp) -model.plot_lines(proj_name+'_lines', display=disp) +model.plot_geometry(proj_name+'_geometry', display=show_gui) +model.plot_parts(proj_name+'_parts', display=show_gui) +model.plot_areas(proj_name+'_areas', display=show_gui) +model.plot_lines(proj_name+'_lines', display=show_gui) # set loads and constraints model.set_constr('fix',pipe.left,'y') @@ -62,30 +72,31 @@ model.set_contact_linear(plate_bottom, arc_outer, kval) # set the element type and mesh database -model.set_eshape('quad', 2) +model.set_eshape(eshape, 2) model.set_etype('plstrain', pipe, pipe_length) model.set_etype('plstrain', plate, pipe_length) -model.set_ediv(['L1','L3', 'L4', 'L6'], wall_elements) # set element divisions +# set element divisions +model.set_ediv(['L1','L3', 'L4', 'L6'], wall_elements) model.set_ediv(['L0','L2'], num_arc_eles) # set element divisions model.set_ediv(['L5','L7'], num_plate_eles) model.mesh(1.0, 'gmsh') # mesh 1.0 fineness, smaller is finer -model.plot_elements(proj_name+'_elem', display=disp) # plot part elements -model.plot_constraints(proj_name+'_constr', display=disp) +model.plot_elements(proj_name+'_elem', display=show_gui) +model.plot_constraints(proj_name+'_constr', display=show_gui) # make and solve the model prob = pyc.Problem(model, 'struct') prob.solve() # Plot results -fields = 'Sx,Sy,S1,S2,S3,Seqv,ux,uy,utot' # store the fields to plot +fields = 'Sx,Sy,S1,S2,S3,Seqv,ux,uy,utot' # store the fields to plot fields = fields.split(',') for field in fields: fname = proj_name+'_'+field - prob.rfile.nplot(field, fname, display=disp) + prob.rfile.nplot(field, fname, display=False) model.view.select(pipe) model.view.allsel_under('parts') for field in fields: fname = proj_name+'_PIPE_'+field - prob.rfile.nplot(field, fname, display=disp) + prob.rfile.nplot(field, fname, display=False) diff --git a/examples/rounded-square-ccw.py b/examples/rounded-square-ccw.py index 29b9b09..e969c99 100644 --- a/examples/rounded-square-ccw.py +++ b/examples/rounded-square-ccw.py @@ -1,10 +1,21 @@ #!/usr/bin/env python3 +import sys + import pycalculix as pyc model_name = 'rounded-square-ccw' model = pyc.FeaModel(model_name) model.set_units('m') +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + length = 2 thickness = 1 radius = 0.5 @@ -21,13 +32,14 @@ part.draw_line_to(radius_inner, axial) part.fillet_lines(line_1, line_2, radius) -model.plot_geometry(model_name + '_geom') +model.plot_geometry(model_name + '_geom', display=show_gui) part.chunk() -model.plot_geometry(model_name + '_chunked') +model.plot_geometry(model_name + '_chunked', display=show_gui) model.view.print_summary() model.set_etype('axisym', part) +model.set_eshape(eshape, 2) model.mesh(1.0, 'gmsh') -model.plot_elements(model_name+'_elements') +model.plot_elements(model_name+'_elements', display=show_gui) model.view.print_summary() model.print_summary() diff --git a/examples/rounded-square-cw.py b/examples/rounded-square-cw.py index 33a888c..5858962 100644 --- a/examples/rounded-square-cw.py +++ b/examples/rounded-square-cw.py @@ -1,10 +1,21 @@ #!/usr/bin/env python3 +import sys + import pycalculix as pyc model_name = 'rounded-square-cw' model = pyc.FeaModel(model_name) model.set_units('m') +# set whether or not to show gui plots +show_gui = True +if '-nogui' in sys.argv: + show_gui = False +# set element shape +eshape = 'quad' +if '-tri' in sys.argv: + eshape = 'tri' + length = 2 thickness = 1 radius = 0.5 @@ -21,13 +32,14 @@ part.draw_line_to(radius_inner, axial) part.fillet_lines(line_1, line_2, radius) -model.plot_geometry(model_name + '_geom') +model.plot_geometry(model_name + '_geom', display=show_gui) part.chunk() -model.plot_geometry(model_name + '_chunked') +model.plot_geometry(model_name + '_chunked', display=show_gui) model.view.print_summary() model.set_etype('axisym', part) +model.set_eshape(eshape, 2) model.mesh(1.0, 'gmsh') -model.plot_elements(model_name+'_elements') +model.plot_elements(model_name+'_elements', display=show_gui) model.view.print_summary() model.print_summary() diff --git a/examples/test.dxf b/examples/test.dxf deleted file mode 100644 index d63fd98..0000000 --- a/examples/test.dxf +++ /dev/null @@ -1,1458 +0,0 @@ - 0 -SECTION - 2 -HEADER - 9 -$ACADVER - 1 -AC1009 - 9 -$INSBASE - 10 -0.0 - 20 -0.0 - 30 -0.0 - 9 -$EXTMIN - 10 --1.6190615121363401 - 20 --1.625264346820358 - 30 --0.0000000349181948 - 9 -$EXTMAX - 10 -20.001438456765189 - 20 -55.016611135943663 - 30 -0.0000000814935747 - 9 -$LIMMIN - 10 -0.0 - 20 -0.0 - 9 -$LIMMAX - 10 -420.0 - 20 -297.0 - 9 -$ORTHOMODE - 70 - 1 - 9 -$REGENMODE - 70 - 1 - 9 -$FILLMODE - 70 - 1 - 9 -$QTEXTMODE - 70 - 0 - 9 -$MIRRTEXT - 70 - 0 - 9 -$DRAGMODE - 70 - 2 - 9 -$LTSCALE - 40 -1.0 - 9 -$OSMODE - 70 - 36 - 9 -$ATTMODE - 70 - 1 - 9 -$TEXTSIZE - 40 -2.5 - 9 -$TRACEWID - 40 -1.0 - 9 -$TEXTSTYLE - 7 -STANDARD - 9 -$CLAYER - 8 -0 - 9 -$CELTYPE - 6 -BYLAYER - 9 -$CECOLOR - 62 - 256 - 9 -$DIMSCALE - 40 -1.0 - 9 -$DIMASZ - 40 -2.5 - 9 -$DIMEXO - 40 -0.625 - 9 -$DIMDLI - 40 -3.75 - 9 -$DIMRND - 40 -0.0 - 9 -$DIMDLE - 40 -0.0 - 9 -$DIMEXE - 40 -1.25 - 9 -$DIMTP - 40 -0.0 - 9 -$DIMTM - 40 -0.0 - 9 -$DIMTXT - 40 -2.5 - 9 -$DIMCEN - 40 -2.5 - 9 -$DIMTSZ - 40 -0.0 - 9 -$DIMTOL - 70 - 0 - 9 -$DIMLIM - 70 - 0 - 9 -$DIMTIH - 70 - 0 - 9 -$DIMTOH - 70 - 0 - 9 -$DIMSE1 - 70 - 0 - 9 -$DIMSE2 - 70 - 0 - 9 -$DIMTAD - 70 - 1 - 9 -$DIMZIN - 70 - 8 - 9 -$DIMBLK - 1 - - 9 -$DIMASO - 70 - 1 - 9 -$DIMSHO - 70 - 1 - 9 -$DIMPOST - 1 - - 9 -$DIMAPOST - 1 - - 9 -$DIMALT - 70 - 0 - 9 -$DIMALTD - 70 - 3 - 9 -$DIMALTF - 40 -0.03937007874016 - 9 -$DIMLFAC - 40 -1.0 - 9 -$DIMTOFL - 70 - 1 - 9 -$DIMTVP - 40 -0.0 - 9 -$DIMTIX - 70 - 0 - 9 -$DIMSOXD - 70 - 0 - 9 -$DIMSAH - 70 - 0 - 9 -$DIMBLK1 - 1 - - 9 -$DIMBLK2 - 1 - - 9 -$DIMSTYLE - 2 -ISO-25 - 9 -$DIMCLRD - 70 - 0 - 9 -$DIMCLRE - 70 - 0 - 9 -$DIMCLRT - 70 - 0 - 9 -$DIMTFAC - 40 -1.0 - 9 -$DIMGAP - 40 -0.625 - 9 -$LUNITS - 70 - 2 - 9 -$LUPREC - 70 - 4 - 9 -$SKETCHINC - 40 -1.0 - 9 -$FILLETRAD - 40 -2.0 - 9 -$AUNITS - 70 - 0 - 9 -$AUPREC - 70 - 0 - 9 -$MENU - 1 -. - 9 -$ELEVATION - 40 -0.0 - 9 -$PELEVATION - 40 -0.0 - 9 -$THICKNESS - 40 -0.0 - 9 -$LIMCHECK - 70 - 0 - 9 -$BLIPMODE - 70 - 0 - 9 -$CHAMFERA - 40 -0.0 - 9 -$CHAMFERB - 40 -0.0 - 9 -$SKPOLY - 70 - 0 - 9 -$TDCREATE - 40 -2456994.5021542478 - 9 -$TDUPDATE - 40 -2457065.5294971881 - 9 -$TDINDWG - 40 -0.0367689931 - 9 -$TDUSRTIMER - 40 -0.0367687153 - 9 -$USRTIMER - 70 - 1 - 9 -$ANGBASE - 50 -0.0 - 9 -$ANGDIR - 70 - 0 - 9 -$PDMODE - 70 - 0 - 9 -$PDSIZE - 40 -0.0 - 9 -$PLINEWID - 40 -0.0 - 9 -$COORDS - 70 - 1 - 9 -$SPLFRAME - 70 - 0 - 9 -$SPLINETYPE - 70 - 6 - 9 -$SPLINESEGS - 70 - 8 - 9 -$ATTDIA - 70 - 0 - 9 -$ATTREQ - 70 - 1 - 9 -$HANDLING - 70 - 1 - 9 -$HANDSEED - 5 -635 - 9 -$SURFTAB1 - 70 - 6 - 9 -$SURFTAB2 - 70 - 6 - 9 -$SURFTYPE - 70 - 6 - 9 -$SURFU - 70 - 6 - 9 -$SURFV - 70 - 6 - 9 -$UCSNAME - 2 - - 9 -$UCSORG - 10 -0.0 - 20 -0.0 - 30 -0.0 - 9 -$UCSXDIR - 10 -1.0 - 20 -0.0 - 30 -0.0 - 9 -$UCSYDIR - 10 -0.0 - 20 -1.0 - 30 -0.0 - 9 -$PUCSNAME - 2 - - 9 -$PUCSORG - 10 -0.0 - 20 -0.0 - 30 -0.0 - 9 -$PUCSXDIR - 10 -1.0 - 20 -0.0 - 30 -0.0 - 9 -$PUCSYDIR - 10 -0.0 - 20 -1.0 - 30 -0.0 - 9 -$USERI1 - 70 - 0 - 9 -$USERI2 - 70 - 0 - 9 -$USERI3 - 70 - 0 - 9 -$USERI4 - 70 - 0 - 9 -$USERI5 - 70 - 0 - 9 -$USERR1 - 40 -0.0 - 9 -$USERR2 - 40 -0.0 - 9 -$USERR3 - 40 -0.0 - 9 -$USERR4 - 40 -0.0 - 9 -$USERR5 - 40 -0.0 - 9 -$WORLDVIEW - 70 - 1 - 9 -$SHADEDGE - 70 - 3 - 9 -$SHADEDIF - 70 - 70 - 9 -$TILEMODE - 70 - 1 - 9 -$MAXACTVP - 70 - 64 - 9 -$PLIMCHECK - 70 - 0 - 9 -$PEXTMIN - 10 -0.0 - 20 -0.0 - 30 -0.0 - 9 -$PEXTMAX - 10 -0.0 - 20 -0.0 - 30 -0.0 - 9 -$PLIMMIN - 10 -0.0 - 20 -0.0 - 9 -$PLIMMAX - 10 -12.0 - 20 -9.0 - 9 -$UNITMODE - 70 - 0 - 9 -$VISRETAIN - 70 - 1 - 9 -$PLINEGEN - 70 - 0 - 9 -$PSLTSCALE - 70 - 1 - 0 -ENDSEC - 0 -SECTION - 2 -TABLES - 0 -TABLE - 2 -VPORT - 70 - 1 - 0 -VPORT - 2 -*ACTIVE - 70 - 0 - 10 -0.0 - 20 -0.0 - 11 -1.0 - 21 -1.0 - 12 -27.785034660948671 - 22 -21.708239725689729 - 13 -0.0 - 23 -0.0 - 14 -10.0 - 24 -10.0 - 15 -10.0 - 25 -10.0 - 16 -0.0 - 26 -0.0 - 36 -1.0 - 17 -0.0 - 27 -0.0 - 37 -0.0 - 40 -91.236312872102275 - 41 -2.1283236994219652 - 42 -50.0 - 43 -0.0 - 44 -0.0 - 50 -0.0 - 51 -0.0 - 71 - 0 - 72 - 1000 - 73 - 1 - 74 - 3 - 75 - 0 - 76 - 1 - 77 - 0 - 78 - 0 - 0 -ENDTAB - 0 -TABLE - 2 -LTYPE - 70 - 2 - 0 -LTYPE - 2 -CONTINUOUS - 70 - 0 - 3 -Solid line - 72 - 65 - 73 - 0 - 40 -0.0 - 0 -ENDTAB - 0 -TABLE - 2 -LAYER - 70 - 3 - 0 -LAYER - 2 -0 - 70 - 0 - 62 - 7 - 6 -CONTINUOUS - 0 -LAYER - 2 -ALL_CUT_FEAT - 70 - 0 - 62 - 7 - 6 -CONTINUOUS - 0 -LAYER - 2 -DEFPOINTS - 70 - 0 - 62 - 7 - 6 -CONTINUOUS - 0 -ENDTAB - 0 -TABLE - 2 -STYLE - 70 - 2 - 0 -STYLE - 2 -STANDARD - 70 - 0 - 40 -0.0 - 41 -1.0 - 50 -0.0 - 71 - 0 - 42 -2.5 - 3 -txt - 4 - - 0 -STYLE - 2 -ANNOTATIVE - 70 - 0 - 40 -0.0 - 41 -1.0 - 50 -0.0 - 71 - 0 - 42 -0.2 - 3 -txt - 4 - - 0 -ENDTAB - 0 -TABLE - 2 -VIEW - 70 - 1 - 0 -ENDTAB - 0 -TABLE - 2 -UCS - 70 - 0 - 0 -ENDTAB - 0 -TABLE - 2 -APPID - 70 - 9 - 0 -APPID - 2 -ACAD - 70 - 0 - 0 -APPID - 2 -ACADANNOTATIVE - 70 - 0 - 0 -APPID - 2 -ACAD_PSEXT - 70 - 0 - 0 -APPID - 2 -ACADANNOPO - 70 - 0 - 0 -APPID - 2 -ACAD_DSTYLE_DIMJAG - 70 - 0 - 0 -APPID - 2 -ACAD_DSTYLE_DIMTALN - 70 - 0 - 0 -APPID - 2 -ACAD_MLEADERVER - 70 - 0 - 0 -APPID - 2 -ACAD_NAV_VCDISPLAY - 70 - 0 - 0 -APPID - 2 -ACAD_EXEMPT_FROM_CAD_STANDARDS - 70 - 0 - 0 -ENDTAB - 0 -TABLE - 2 -DIMSTYLE - 70 - 4 - 0 -DIMSTYLE - 2 -STANDARD - 70 - 0 - 3 - - 4 - - 5 - - 6 - - 7 - - 40 -1.0 - 41 -0.18 - 42 -0.0625 - 43 -0.38 - 44 -0.18 - 45 -0.0 - 46 -0.0 - 47 -0.0 - 48 -0.0 -140 -0.18 -141 -0.09 -142 -0.0 -143 -25.399999999999999 -144 -1.0 -145 -0.0 -146 -1.0 -147 -0.09 - 71 - 0 - 72 - 0 - 73 - 1 - 74 - 1 - 75 - 0 - 76 - 0 - 77 - 0 - 78 - 0 -170 - 0 -171 - 2 -172 - 0 -173 - 0 -174 - 0 -175 - 0 -176 - 0 -177 - 0 -178 - 0 - 0 -DIMSTYLE - 2 -ANNOTATIVE - 70 - 0 - 3 - - 4 - - 5 - - 6 - - 7 - - 40 -1.0 - 41 -0.18 - 42 -0.0625 - 43 -0.38 - 44 -0.18 - 45 -0.0 - 46 -0.0 - 47 -0.0 - 48 -0.0 -140 -0.18 -141 -0.09 -142 -0.0 -143 -25.399999999999999 -144 -1.0 -145 -0.0 -146 -1.0 -147 -0.09 - 71 - 0 - 72 - 0 - 73 - 1 - 74 - 1 - 75 - 0 - 76 - 0 - 77 - 0 - 78 - 0 -170 - 0 -171 - 2 -172 - 0 -173 - 0 -174 - 0 -175 - 0 -176 - 0 -177 - 0 -178 - 0 - 0 -DIMSTYLE - 2 -ISO-25 - 70 - 0 - 3 - - 4 - - 5 - - 6 - - 7 - - 40 -1.0 - 41 -2.5 - 42 -0.625 - 43 -3.75 - 44 -1.25 - 45 -0.0 - 46 -0.0 - 47 -0.0 - 48 -0.0 -140 -2.5 -141 -2.5 -142 -0.0 -143 -0.03937007874016 -144 -1.0 -145 -0.0 -146 -1.0 -147 -0.625 - 71 - 0 - 72 - 0 - 73 - 0 - 74 - 0 - 75 - 0 - 76 - 0 - 77 - 1 - 78 - 8 -170 - 0 -171 - 3 -172 - 1 -173 - 0 -174 - 0 -175 - 0 -176 - 0 -177 - 0 -178 - 0 - 0 -ENDTAB - 0 -ENDSEC - 0 -SECTION - 2 -BLOCKS - 0 -BLOCK - 8 -0 - 2 -$MODEL_SPACE - 70 - 0 - 10 -0.0 - 20 -0.0 - 30 -0.0 - 3 -$MODEL_SPACE - 1 - - 0 -ENDBLK - 5 -513 - 8 -0 - 0 -BLOCK - 67 - 1 - 8 -0 - 2 -$PAPER_SPACE - 70 - 0 - 10 -0.0 - 20 -0.0 - 30 -0.0 - 3 -$PAPER_SPACE - 1 - - 0 -ENDBLK - 5 -50F - 67 - 1 - 8 -0 - 0 -ENDSEC - 0 -SECTION - 2 -ENTITIES - 0 -LINE - 5 -52D - 8 -0 - 10 -3.0 - 20 -20.0 - 30 -0.0 - 11 -20.0 - 21 -20.0 - 31 -0.0 - 0 -LINE - 5 -52E - 8 -0 - 10 -0.0 - 20 -23.0 - 30 -0.0 - 11 -0.0 - 21 -47.0 - 31 -0.0 - 0 -LINE - 5 -52F - 8 -0 - 10 -5.0 - 20 -24.0 - 30 -0.0 - 11 -20.0 - 21 -24.0 - 31 -0.0 - 0 -ARC - 5 -534 - 8 -0 - 10 -5.0 - 20 -26.0 - 30 -0.0 - 40 -2.0 - 50 -90.0 - 51 -270.0 - 0 -LINE - 5 -535 - 8 -0 - 10 -5.0 - 20 -28.0 - 30 -0.0 - 11 -5.0 - 21 -43.0 - 31 -0.0 - 0 -ARC - 5 -536 - 8 -0 - 10 -3.0 - 20 -23.0 - 30 -0.0 - 40 -3.0 - 50 -180.0 - 51 -270.0 - 0 -LINE - 5 -538 - 8 -0 - 10 -20.0 - 20 -20.0 - 30 -0.0 - 11 -20.0 - 21 -24.0 - 31 -0.0 - 0 -LINE - 5 -539 - 8 -0 - 10 -7.0 - 20 -45.0 - 30 -0.0 - 11 -20.0 - 21 -45.0 - 31 -0.0 - 0 -LINE - 5 -53A - 8 -0 - 10 -3.0 - 20 -50.0 - 30 -0.0 - 11 -13.0 - 21 -50.0 - 31 -0.0 - 0 -LINE - 5 -53B - 8 -0 - 10 -16.0 - 20 -55.0 - 30 -0.0 - 11 -20.0 - 21 -55.0 - 31 -0.0 - 0 -LINE - 5 -53C - 8 -0 - 10 -20.0 - 20 -55.0 - 30 -0.0 - 11 -20.0 - 21 -45.0 - 31 -0.0 - 0 -LINE - 5 -53E - 8 -0 - 10 -16.0 - 20 -55.0 - 30 -0.0 - 11 -16.0 - 21 -53.0 - 31 -0.0 - 0 -ARC - 5 -53F - 8 -0 - 10 -13.0 - 20 -53.0 - 30 -0.0 - 40 -3.0 - 50 -270.0 - 51 -0.0 - 0 -ARC - 5 -540 - 8 -0 - 10 -3.0 - 20 -47.0 - 30 -0.0 - 40 -3.0 - 50 -90.0 - 51 -180.0 - 0 -ARC - 5 -541 - 8 -0 - 10 -7.0 - 20 -43.0 - 30 -0.0 - 40 -2.0 - 50 -90.0 - 51 -180.0 - 0 -ENDSEC - 0 -EOF diff --git a/pycalculix/cadimporter.py b/pycalculix/cadimporter.py index 36ee6e2..2d83b3d 100644 --- a/pycalculix/cadimporter.py +++ b/pycalculix/cadimporter.py @@ -1,7 +1,9 @@ """This module stores the CadImporter class, which is used to load CAD parts.""" +import collections import math import os +import pdb # needed to prevent dxfgrabber from crashing on import os.environ['DXFGRABBER_CYTHON'] = 'OFF' import dxfgrabber # needed for dxf files @@ -48,41 +50,49 @@ def load(self): if self.__fname == '': print('You must pass in a file name to load!') return [] - else: - fname_list = self.__fname.split('.') - ext = fname_list[1] - first_pt = None - if len(self.__fea.points) > 0: - first_pt = self.__fea.points[0] - if ext == 'dxf': - parts = self.__load_dxf() - elif ext in ['brep', 'brp', 'iges', 'igs', 'step', 'stp']: - self.__make_geo() - parts = self.__load_geo() - last_pt = None - if first_pt != None: - if len(self.__fea.points) > 2: - last_pt = self.__fea.points[-1] - if self.__scale != '': - # call scale - pass - return parts - def __fix_point(self, point): + fname_list = self.__fname.split('.') + ext = fname_list[1] + first_pt = None + if len(self.__fea.points) > 0: + first_pt = self.__fea.points[0] + if ext == 'dxf': + parts = self.__load_dxf() + elif ext in ['brep', 'brp', 'iges', 'igs', 'step', 'stp']: + self.__make_geo() + parts = self.__load_geo() + last_pt = None + if first_pt != None: + if len(self.__fea.points) > 2: + last_pt = self.__fea.points[-1] + if self.__scale != '': + # call scale + pass + return parts + + def __fix_tuple(self, xy_tup): """Adjusts the point to be in the right plane (yx)""" if self.__swapxy: - return geometry.Point(point.y, point.x) - return point + return xy_tup[::-1] + return xy_tup - def __get_pt(self, points, point): - """Returns a point if it is within accuracy of point in points""" - for realpoint in points: - dist = point - realpoint - dist = dist.length() + @staticmethod + def __find_make_pt(xy_tup, points_dict): + """ + Returns a point if it exists within geometry.ACC + or makes, stores and returns the point if it doesn't exist + """ + point = points_dict.get(xy_tup) + if point is not None: + return point + xy_point = geometry.Point(xy_tup[0], xy_tup[1]) + for realpoint in points_dict.values(): + dist = (xy_point - realpoint).length() if dist < geometry.ACC: return realpoint - return point - + points_dict[xy_tup] = xy_point + return xy_point + def __get_pts_lines(self, lines, arcs): """Returns a set of points, and a list of Lines and Arcs @@ -91,32 +101,31 @@ def __get_pts_lines(self, lines, arcs): arcs (dxfgrabber ARC list): dxf arcs Returns: - list: [points, list of Line and Arc] + list: [list of points, list of Line and Arc] """ # store unique points - point_set = set() + points_dict = {} all_lines = [] for ind, line in enumerate(lines): - start = geometry.Point(line.start[0], line.start[1]) - end = geometry.Point(line.end[0], line.end[1]) - start, end = self.__fix_point(start), self.__fix_point(end) - point_set.update([start, end]) + tup = self.__fix_tuple((line.start[0], line.start[1])) + start = self.__find_make_pt(tup, points_dict) + tup = self.__fix_tuple((line.end[0], line.end[1])) + end = self.__find_make_pt(tup, points_dict) line = geometry.Line(start, end) all_lines.append(line) for ind, arc in enumerate(arcs): # dxfgrabber arcs are stored ccw when looking at xy plane # x horizontal # y vertical - center = geometry.Point(arc.center[0], arc.center[1]) - sign = 1 - if self.__swapxy == False: - sign = -1 - center = self.__fix_point(center) - point_set.add(center) - startangle = arc.startangle*sign - endangle = arc.endangle*sign + tup = self.__fix_tuple((arc.center[0], arc.center[1])) + center = self.__find_make_pt(tup, points_dict) + sign = -1 + if self.__swapxy: + sign = 1 + startangle = arc.start_angle*sign + endangle = arc.end_angle*sign angle = endangle - startangle - if arc.endangle < arc.startangle: + if arc.end_angle < arc.start_angle: angle = angle + 360*sign """ print('---------------------------------------') @@ -129,41 +138,45 @@ def __get_pts_lines(self, lines, arcs): start_vect = geometry.Point(0, arc.radius) if self.__swapxy == False: start_vect = geometry.Point(arc.radius, 0) - start_vect.rot_ccw_deg(arc.startangle*sign) + start_vect.rot_ccw_deg(arc.start_angle*sign) end_vect = geometry.Point(0, arc.radius) if self.__swapxy == False: end_vect = geometry.Point(arc.radius, 0) - end_vect.rot_ccw_deg(arc.endangle*sign) + end_vect.rot_ccw_deg(arc.end_angle*sign) start = center + start_vect + start_tup = (start.x, start.y) end = center + end_vect - start = self.__get_pt(point_set, start) - end = self.__get_pt(point_set, end) - point_set.update([start, end]) + end_tup = (end.x, end.y) + start = self.__find_make_pt(start_tup, points_dict) + end = self.__find_make_pt(end_tup, points_dict) rvect = start - center if abs(angle) <= 90: arc = geometry.Arc(start, end, center) all_lines.append(arc) - #print('1 arc made') + print('1 arc made') + continue + #print(' %s' % arc) + pieces = math.ceil(abs(angle)/90) + print('%i arcs being made' % pieces) + points = [start, end] + # 2 pieces need 3 points, we have start + end already --> 1 pt + inserts = pieces + 1 - 2 + piece_ang = angle/pieces + #print('piece_ang = %f' % piece_ang) + while inserts > 0: + rvect.rot_ccw_deg(piece_ang) + point = center + rvect + tup = (point.x, point.y) + point = self.__find_make_pt(tup, points_dict) + points.insert(-1, point) + inserts = inserts - 1 + for ind in range(len(points)-1): #print(' %s' % arc) - else: - pieces = math.ceil(abs(angle)/90) - #print('%i arcs being made' % pieces) - points = [start, end] - # 2 pieces need 3 points, we have start + end already --> 1 pt - inserts = pieces + 1 - 2 - piece_ang = angle/pieces - #print('piece_ang = %f' % piece_ang) - while inserts > 0: - rvect.rot_ccw_deg(piece_ang) - point = center + rvect - points.insert(-1, point) - inserts = inserts - 1 - for ind in range(len(points)-1): - point_set.update([points[ind], points[ind+1]]) - arc = geometry.Arc(points[ind], points[ind+1], center) - #print(' %s' % arc) - all_lines.append(arc) - return [list(point_set), all_lines] + arc = geometry.Arc(points[ind], points[ind+1], center) + all_lines.append(arc) + for line in all_lines: + line.save_to_points() + return [list(points_dict.values()), all_lines] def __make_geo(self): """Makes a gmsh geo file given a step, iges, or brep input""" @@ -189,13 +202,19 @@ def __load_geo(self): # select two segments # draw normal lines # find intersections, that is the center - + + @staticmethod + def __dangling_points(all_points): + return [point for point in all_points + if len(point.lines) == 1 and not point.arc_center] + def __load_dxf(self): """Loads in a dxf file and returns a list of parts Returns: list: list of Part """ + # pdb.set_trace() print('Loading file: %s' % self.__fname) dwg = dxfgrabber.readfile(self.__fname) lines = [item for item in dwg.entities if item.dxftype == 'LINE'] @@ -206,53 +225,68 @@ def __load_dxf(self): print('File read.') print('Loaded %i lines' % len(lines)) print('Loaded %i arcs' % len(arcs)) - + print('Loaded %i line segments, lines or arcs' % + (len(lines)+len(arcs))) # get all points and Line and Arc using pycalculix entities + print('Converting to pycalculix lines arcs and points ...') all_points, all_lines = self.__get_pts_lines(lines, arcs) - # the index of the point in the set can be used as a hash - lines_from_ptind = {} - for line in all_lines: - ind1 = all_points.index(line.pt(0)) - ind2 = all_points.index(line.pt(1)) - for ind in [ind1, ind2]: - if ind not in lines_from_ptind: - lines_from_ptind[ind] = [line] - else: - if line not in lines_from_ptind[ind]: - lines_from_ptind[ind].append(line) + print('Loaded %i line segments, lines or arcs' % len(all_lines)) + print('Loaded %i points' % len(all_points)) + # for point in all_points: + # print('%s %s' % (point, point.lines)) + # for line in all_lines: + # print('%s %s' % (line, line.points)) - # make line loops now - loops = [] + # remove all lines that are not part of areas + dangling_points = self.__dangling_points(all_points) + # pdb.set_trace() + pruned_geometry = bool(dangling_points) + while dangling_points: + for point in dangling_points: + all_points.remove(point) + print('Removed point= %s' % point) + dangling_line = list(point.lines)[0] + point.unset_line(dangling_line) + if dangling_line in all_lines: + all_lines.remove(dangling_line) + print('Removed line= %s' % dangling_line) + dangling_points = self.__dangling_points(all_points) + if pruned_geometry: + print('Remaining line segments: %i' % len(all_lines)) + print('Remaining points: %i' % len(all_points)) + + # make line all_loops now + all_loops = [] line = all_lines[0] this_loop = geometry.LineLoop() while len(all_lines) > 0: this_loop.append(line) - if line in all_lines: - all_lines.remove(line) - point = line.pt(1) - ind = all_points.index(point) - pt_lines = lines_from_ptind[ind] - if line in pt_lines: - pt_lines.remove(line) - if len(pt_lines) == 1: - # we have the next line - next_line = pt_lines[0] - if line.pt(1) != next_line.pt(0): - next_line.reverse() - line = next_line - elif len(pt_lines) > 1: - print('One point was connected to > 2 lines.') - print('Only import simple part loops, or surfaces.') + all_lines.remove(line) if this_loop.closed == True: - loops.append(this_loop) + all_loops.append(this_loop) this_loop = geometry.LineLoop() + if all_lines: + line = all_lines[0] + continue + point = line.pt(1) + other_lines = point.lines - set([line]) + if len(other_lines) > 1: + # note: one could exclude connected segment nodes + # make disconnected line all_loops, then have another + # loop to connect thos disconnected line all_loops + print('One point was connected to > 2 lines.') + print('Only import simple part all_loops, or surfaces.') + raise Exception('Import geometry is too complex') + next_line = list(other_lines)[0] + if line.pt(1) != next_line.pt(0): + next_line.reverse() + line = next_line - # check loops to see if one is inside another - # if no loops inside self, then self is a part - # each part in parts is a list of line loops [exterior, hole1, hole2] - parts = [] - for ind, loop in enumerate(loops): - other_loops = loops[ind+1:] + # find exterior loops + exterior_loops = [] + for ind, loop in enumerate(all_loops): + other_loops = all_loops[ind+1:] + other_loops.extend(exterior_loops) is_exterior = True for other_loop in other_loops: if loop.inside(other_loop): @@ -260,29 +294,29 @@ def __load_dxf(self): break if is_exterior: # exterior must be clockwise - if loop.ccw == True: + if loop.ccw: loop.reverse() - parts.append(loop) - # remove the exterior loops from our loops list - for loop in parts: - loops.remove(loop) - # now place the child loops under the part exterior loops - for ind in range(len(parts)): - exterior_loop = parts[ind] - holes = [] + exterior_loops.append(loop) + # remove the found part exterior loops from all_loops + for exterior_loop in exterior_loops: + all_loops.remove(exterior_loop) + # each part in parts is a list of line all_loops + # [exterior, hole1, hole2] + parts = [[exterior_loop] for exterior_loop in exterior_loops] + # now place the child hole loops after the part exterior loop + for part_loops in parts: + exterior_loop = part_loops[0] # find child holes - for loop in loops: - if loop.inside(exterior_loop): + for hole_loop in all_loops: + if hole_loop.inside(exterior_loop): + hole_loop.hole = True # holes must be ccw - loop.hole = True - if loop.ccw == False: - loop.reverse() - holes.append(loop) - loop_list = [exterior_loop] + holes - parts[ind] = loop_list + if not hole_loop.ccw: + hole_loop.reverse() + part_loops.append(hole_loop) # remove child holes from loop list - for hole in holes: - loops.remove(hole) + for hole_loop in part_loops[1:]: + all_loops.remove(hole_loop) # make parts parts_list = [] diff --git a/pycalculix/dmginstall.sh b/pycalculix/dmginstall.sh new file mode 100755 index 0000000..1ea95d4 --- /dev/null +++ b/pycalculix/dmginstall.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# https://gist.github.com/afgomez/4172338 +# Downloads and install a .dmg from a URL +# +# Usage +# $ dmginstall [url] +# +# For example, for installing alfred.app +# $ dmginstall http://cachefly.alfredapp.com/alfred_1.3.1_261.dmg +# +# TODO +# - currently only handles .dmg with .app folders, not .pkg files +# - handle .zip files as well + + +if [[ $# -lt 1 ]]; then + echo "Usage: dmginstall [url]" + exit 1 +fi + +url=$* + +# Generate a random file name +tmp_file=/tmp/`openssl rand -base64 10 | tr -dc '[:alnum:]'`.dmg +apps_folder='/Applications' + +# Download file +echo "Downloading $url..." +curl -# -L -o $tmp_file $url + +echo "Mounting image..." +volume=`hdiutil mount $tmp_file | tail -n1 | perl -nle '/(\/Volumes\/[^ ]+)/; print $1'` + +# Locate .app folder and move to /Applications +app=`find $volume/. -name *.app -maxdepth 1 -type d -print0` +echo "Copying `echo $app | awk -F/ '{print $NF}'` into $apps_folder..." +cp -ir $app $apps_folder + +# Unmount volume, delete temporal file +echo "Cleaning up..." +hdiutil unmount $volume -quiet +rm $tmp_file + +echo "Done!" diff --git a/pycalculix/feamodel.py b/pycalculix/feamodel.py index d069230..2af75c3 100644 --- a/pycalculix/feamodel.py +++ b/pycalculix/feamodel.py @@ -1635,7 +1635,7 @@ def mesh(self, size=1.0, meshmode='fineness', mesher='gmsh'): elif mesher == 'cgx': self.__mesh_cgx(size) - def __mesh_gmsh(self, size, meshmode): + def __mesh_gmsh(self, size, meshmode, timeout=20): """Meshes all parts using the Gmsh mesher. Args: @@ -1656,6 +1656,8 @@ def __mesh_gmsh(self, size, meshmode): - 'fineness': adapt mesh size to geometry - 'esize': keep explicitly defined element size + timeout (int): time in seconds before the process throws a + subprocess.TimeoutExpired """ geo = [] @@ -1779,8 +1781,8 @@ def __mesh_gmsh(self, size, meshmode): # use this so small circles are meshed finely geo.append('Mesh.CharacteristicLengthFromCurvature = 1;') geo.append('Mesh.CharacteristicLengthFromPoints = 1;') - geo.append('Mesh.Algorithm = 2; //delauny') #okay for quads - # geo.append('Mesh.Algorithm = 8; //delquad = delauny for quads') + # geo.append('Mesh.Algorithm = 2; //delauny') #okay for quads + geo.append('Mesh.Algorithm = 8; //delquad = delauny for quads') geo.append('Mesh.ElementOrder = ' +str(order) +'; //linear or second set here') @@ -1798,22 +1800,24 @@ def __mesh_gmsh(self, size, meshmode): outfile.close() print('File: %s was written' % fname) - # run file in bg mode, -2 is 2d mesh + # run file in bg mode, -2 is 2d mesh, makes required inp file runstr = "%s %s -2 -o %s" % (environment.GMSH, fname, fout) print(runstr) - subprocess.call(runstr, shell=True) + subprocess.check_call(runstr, timeout=timeout, shell=True) print('File: %s was written' % fout) print('Meshing done!') - # write gmsh msh file - runstr = "%s %s -2 -o %s" % (environment.GMSH, fname, self.fname+'.msh') - subprocess.call(runstr, shell=True) + # write gmsh msh file, for manual checking only + # not required by pycalculix + runstr = "%s %s -2 -o %s" % (environment.GMSH, fname, + self.fname+'.msh') + subprocess.check_call(runstr, timeout=timeout, shell=True) print('File: %s.msh was written' % self.fname) # read in the calculix mesh self.__read_inp(self.fname+'.inp') - def __mesh_cgx(self, size, meshmode): + def __mesh_cgx(self, size, meshmode, timeout=20): """Meshes all parts using the Calculix cgx mesher. Args: @@ -1833,6 +1837,9 @@ def __mesh_cgx(self, size, meshmode): - 'fineness': adapt mesh size to geometry - 'esize': keep explicitly defined element size NOT TESTED WITH CGX + + timeout (int): time in seconds before the process throws a + subprocess.TimeoutExpired """ fbd = [] comps = [] @@ -1957,7 +1964,8 @@ def __mesh_cgx(self, size, meshmode): print('File: %s was written' % fname) # run file in bg mode - p = subprocess.call("%s -bg %s" % (environment.CGX, fname), shell=True) + runstr = "%s -bg %s" % (environment.CGX, fname) + p = subprocess.check_call(runstr, timeout=timeout, shell=True) print('Meshing done!') # assemble the output files into a ccx input file diff --git a/pycalculix/geometry.py b/pycalculix/geometry.py index 749d6f5..52d3756 100644 --- a/pycalculix/geometry.py +++ b/pycalculix/geometry.py @@ -55,7 +55,7 @@ class Point(base_classes.Idobj): nodes (Node): a list of nodes in a mesh at this point. List length is 1. nodes (list): list of nodes under point - lines (list): list of lines which use this point + lines (set): set of lines which use this point arc_center (bool): True if this point is an arc center """ @@ -64,7 +64,7 @@ def __init__(self, x, y, z=0): self.y = y self.z = z self.nodes = [] - self.lines = [] + self.lines = set() self.arc_center = False self.on_part = True self.esize = None @@ -149,10 +149,10 @@ def __truediv__(self, other): factor = sum(res)/len(res) return factor - def save_line(self, line): + def set_line(self, line): """Saves the line or arc in the point.""" if line not in self.lines: - self.lines.append(line) + self.lines.add(line) if self.arc_center == True: if len(self.lines) > 1: for line in self.lines: @@ -160,6 +160,12 @@ def save_line(self, line): self.arc_center = False break + def unset_line(self, line): + """Removes the line or arc in the point.""" + if line not in self.lines: + return + self.lines.remove(line) + def length(self): """Returns the length of this point or vector.""" res = (self.x**2 + self.y**2)**0.5 @@ -259,10 +265,10 @@ def __str__(self): """Returns string listing object type, id number, and coordinates""" val = 'Point %s, (x, y)=(%.3f, %.3f)' % (self.get_name(), self.x, self.y) return val - + def set_esize(self, size): self.esize = size - + class Line(base_classes.Idobj): @@ -348,7 +354,7 @@ def length(self): def save_to_points(self): """This stores this line in the line's child points.""" for point in self.allpoints: - point.save_line(self) + point.set_line(self) def set_ediv(self, ediv): """Sets the number of element divisions on the line when meshing. @@ -357,7 +363,7 @@ def set_ediv(self, ediv): ediv (int): number of required elements on this line """ self.ediv = ediv - + def set_esize(self, esize): """Sets the size of mesh elements on the line when meshing. @@ -689,11 +695,11 @@ def set_pt(self, index, point): def set_ediv(self, ediv): """Applies the element divisions onto the parent line.""" self.line.set_ediv(ediv) - + def set_esize(self, esize): """Applies the element size onto the parent line.""" self.line.set_esize(esize) - + def set_lineloop(self, lineloop): """Sets the parent LineLoop""" # this is needed to cascade set ediv up to FEA model and down onto @@ -886,8 +892,8 @@ def length(self): def save_to_points(self): """This stores this line in the line's child points.""" for point in self.allpoints: - point.save_line(self) - + point.set_line(self) + def set_ediv(self, ediv): """Sets the number of element divisions on the arc when meshing. @@ -895,17 +901,17 @@ def set_ediv(self, ediv): ediv (int): number of required elements on this arc """ self.ediv = ediv - + def set_esize(self, esize): """Sets the size of mesh elements on the arc when meshing. Args: esize (float): size of mesh elements on this arc """ - + for i in range(2): self.pt(i).set_esize(esize) - + def signed_copy(self, sign): """Returns a SignArc instance of this Arc with the passed sign.""" return SignArc(self, sign) @@ -1284,7 +1290,7 @@ def set_pt(self, ind, point): def set_ediv(self, ediv): """Apply the element divisions onto the parent line.""" self.line.set_ediv(ediv) - + def set_esize(self, esize): """Applies the element size onto the parent line.""" self.line.set_esize(esize) @@ -2015,7 +2021,7 @@ def set_etype(self, etype): """ self.etype = etype self.set_child_ccxtypes() - + def set_esize(self, esize): """Sets the size of mesh elements on the area when meshing. diff --git a/pycalculix/installer.py b/pycalculix/installer.py index 2b2ffdf..e576046 100644 --- a/pycalculix/installer.py +++ b/pycalculix/installer.py @@ -1,11 +1,14 @@ import glob import os +import pdb import re import shutil import shlex import subprocess import sys +from urllib.parse import urlparse +from urllib.parse import ParseResult import zipfile from distutils.dir_util import copy_tree @@ -13,24 +16,27 @@ import requests +def printos(os, bits): + """Prints the operating system and bit size""" + print('Detected {} {} bit'.format(os, bits)) + def add(): """Installs the fea tools on windows/mac/linux""" osname = None is_64bit = sys.maxsize > 2**32 bitsize_dict = {True: 64, False: 32} bitsize = bitsize_dict[is_64bit] - printos = lambda os, bits=bitsize: print('Detected {} {} bit'.format(os, bits)) if platform == "linux" or platform == "linux2": - printos('Linux') + printos('Linux', bitsize) ubuntu_add() elif platform == "darwin": - printos('Mac OS X') + printos('Mac OS X', bitsize) mac_add() elif platform == "win32": - printos('Windows') + printos('Windows', bitsize) windows_add(bitsize) elif platform == "win64": - printos('Windows') + printos('Windows', bitsize) windows_add(bitsize) print('Done!') @@ -40,23 +46,22 @@ def remove(): is_64bit = sys.maxsize > 2**32 bitsize_dict = {True: 64, False: 32} bitsize = bitsize_dict[is_64bit] - printos = lambda os, bits=bitsize: print('Detected {} {} bit'.format(os, bits)) if platform == "linux" or platform == "linux2": - printos('Linux') + printos('Linux', bitsize) ubuntu_remove() elif platform == "darwin": - printos('Mac OS X') + printos('Mac OS X', bitsize) mac_remove() elif platform == "win32": - printos('Windows') + printos('Windows', bitsize) windows_remove(bitsize) elif platform == "win64": - printos('Windows') + printos('Windows', bitsize) windows_remove(bitsize) print('Done!') def ubuntu_add(): - """Adds programs on ubunut, uses apt""" + """Adds ccx and gmsh programs on ubuntu, uses apt""" gmsh_installed = shutil.which('gmsh') if not gmsh_installed: print('Installing gmsh') @@ -73,7 +78,7 @@ def ubuntu_add(): print('calculix (ccx) present') def ubuntu_remove(): - """Removes programs on ubuntu, uses apt""" + """Removes ccx and gmsh programs on ubuntu, uses apt""" ccx_installed = shutil.which('ccx') if not ccx_installed: print('calculix (ccx) is not on your system') @@ -90,25 +95,33 @@ def ubuntu_remove(): subprocess.check_call(command_line, shell=True) def mac_add(): - """Adds programs on mac, uses brew""" + """Adds ccx and gmsh programs on mac, uses brew""" brew_installed = shutil.which('brew') if not brew_installed: print('Installing brew') - command_line = "/usr/bin/ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"" + url = 'https://raw.githubusercontent.com/Homebrew/install/master/install' + command_line = "/usr/bin/ruby -e \"$(curl -fsSL %s)\"" % url subprocess.check_call(command_line, shell=True) else: print('brew present') gmsh_installed = shutil.which('gmsh') if not gmsh_installed: print('Installing gmsh') - command_line = "brew install gmsh" + folder_path = os.path.dirname(os.path.abspath(__file__)) + dmginstall_path = os.path.join(folder_path, 'dmginstall.sh') + url = 'http://gmsh.info/bin/MacOSX/gmsh-3.0.5-MacOSX.dmg' + command_line = '%s %s' % (dmginstall_path, url) + print('command_line=%s' % command_line) + subprocess.check_call(command_line, shell=True) + gmsh_path = '/Applications/Gmsh.app/Contents/MacOS/gmsh' + command_line = "ln -s %s /usr/local/bin/gmsh" % gmsh_path subprocess.check_call(command_line, shell=True) else: print('gmsh present') ccx_installed = shutil.which('ccx') if not ccx_installed: print('Installing calculix (ccx)') - command_line = "brew install homebrew/science/calculix-ccx" + command_line = "brew install brewsci/science/calculix-ccx" subprocess.check_call(command_line, shell=True) ccx_path = find_brew_binary_location('calculix-ccx', 'ccx') if not ccx_path: @@ -129,11 +142,12 @@ def find_brew_binary_location(package_folder, search_string): return None def mac_remove(): - """Removes programs on mac, uses brew""" + """Removes ccx and gmsh programs on mac, uses brew""" brew_installed = shutil.which('brew') if not brew_installed: print('Installing brew') - command_line = "/usr/bin/ruby -e \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)\"" + url = 'https://raw.githubusercontent.com/Homebrew/install/master/install' + command_line = "/usr/bin/ruby -e \"$(curl -fsSL %s)\"" % url subprocess.check_call(command_line, shell=True) else: print('brew present') @@ -144,34 +158,39 @@ def mac_remove(): print('Removing calculix (ccx)') command_line = "rm /usr/local/bin/ccx" subprocess.check_call(command_line, shell=True) - command_line = "brew uninstall homebrew/science/calculix-ccx" + command_line = "brew uninstall calculix-ccx" subprocess.check_call(command_line, shell=True) gmsh_installed = shutil.which('gmsh') if not gmsh_installed: print('gmsh is not on your system') else: print('Removing gmsh') - command_line = "brew uninstall gmsh" + command_line = "rm /usr/local/bin/gmsh" subprocess.check_call(command_line, shell=True) + command_line = "rm -rf /Applications/Gmsh.app" + subprocess.check_call(command_line, shell=True) + def windows_add(bitsize): - """Adds programs on windows""" + """Adds ccx and gmsh programs on windows""" gmsh_installed = shutil.which('gmsh') if not gmsh_installed: print('Installing gmsh') - win_add_from_url(bitsize, 'http://gmsh.info/bin/Windows/', 'gmsh') + url = 'http://gmsh.info/bin/Windows/' + win_add_gmsh(bitsize, url, 'gmsh', '3.0.5') else: print('gmsh present') ccx_installed = shutil.which('ccx') if not ccx_installed: print('Installing calculix (ccx)') - win_add_ccx(bitsize, "https://sourceforge.net/projects/calculixforwin/files/03.2/", "ccx") + url = 'https://sourceforge.net/projects/calculixforwin/files/03.2/' + win_add_ccx(bitsize, url, 'ccx') else: print('calculix (ccx) present') def windows_remove(bitsize): - """Removes programs on windows""" + """Removes ccx and gmh programs on windows""" gmsh_installed = shutil.which('gmsh') if not gmsh_installed: print('gmsh is not on your system') @@ -204,17 +223,18 @@ def remove_like(search_path, name): elif os.path.isdir(path): shutil.rmtree(path) -def win_add_from_url(bitsize, binaries_url, program_name): +def win_add_gmsh(bitsize, binaries_url, program_name, version_str): """Installs a program from an apache server file listing page""" # Needs the user agent header to exist for it to send back the content user_agent = ('Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:56.0) ' 'Gecko/20100101 Firefox/56.0') headers = {'User-Agent': user_agent} - zipfile_regex = '.*%s.*' % program_name - zipfile_name = zipfile_by_bitsize(binaries_url, headers, zipfile_regex, bitsize) + zipfile_regex = '.*%s-%s.*' % (program_name, version_str) + # Windows 10 64 bit, gmsh version: Gmsh 3.0.7-git-35176e26 hangs + zipfile_name = zipfile_by_bitsize(binaries_url, headers, zipfile_regex, + bitsize) - zipfile_folder_name = zipfile_name.split('.')[0] zipfile_url = binaries_url + zipfile_name print('Downloading %s from %s' % (program_name, zipfile_url)) response = requests.get(zipfile_url, stream=True, headers=headers) @@ -224,6 +244,7 @@ def win_add_from_url(bitsize, binaries_url, program_name): out_file.write(chunk) print('Unzipping %s' % program_name) zip_ref = zipfile.ZipFile(zipfile_name, 'r') + zipfile_folder_name = os.path.dirname(zip_ref.namelist()[0]) zip_ref.extractall(None) zip_ref.close() print('Removing %s zipfile' % program_name) @@ -246,6 +267,7 @@ def win_add_from_url(bitsize, binaries_url, program_name): def zipfile_by_bitsize(binaries_url, headers, zipfile_regex, bitsize): """Returns the url linking to the correct zipfile""" + # this is used by ccx and gmsh res = requests.get(binaries_url, headers=headers) html = res.text urls = re.findall(r'href=[\'"]?([^\'" >]+)', html) @@ -257,16 +279,49 @@ def zipfile_by_bitsize(binaries_url, headers, zipfile_regex, bitsize): url_choices = {32: urls[1], 64: urls[0]} return url_choices[bitsize] -def get_direct_url(source_page, headers): - """Gets the download link from a sf page""" - res = requests.get(source_page, headers=headers) - html = res.text - html = html.replace("\"", "'") - link_text_pos = html.find('direct link') +def href_from_link_text(url, headers, link_text): + """ + Returns the url for a link with link_text description, if this + function fails, it raises an error with tells the users a link + which creates an issue on the pycalculix repo + """ + response = requests.get(url, headers=headers) + html = response.text.replace("\"", "'") + link_text_pos = html.find(link_text) + if link_text_pos == -1: + issue_url = ('https://github.com/spacether/pycalculix/issues/new?title=' + 'CCX%20zip%20download%20fails%20on%20windows&body=Please%2' + '0update%20the%20installer.py%20get_direct_url%20function.' + '%20It%20no%20longer%20works') + raise ValueError('Unable to download file because there was not a link ' + 'with text=\'%s\' on the page at url=%s\nTo fix this, ' + 'please click the \'Submit new issue\' button ' + 'here:\n%s' % (link_text, url, issue_url)) href_pos = html[:link_text_pos].rfind('href') first_char = html.find("'", href_pos)+1 - last_quote = html.find("'", first_char) - return html[first_char:last_quote] + last_char = html.find("'", first_char) + return html[first_char:last_char] + +def get_direct_url(url, headers): + """Gets the zip direct download link from the project download page""" + direct_download_url = href_from_link_text(url, + headers, + 'Problems Downloading') + parsed_download_url = urlparse(direct_download_url) + if parsed_download_url.scheme not in ['http', 'https']: + # url is relative, and is missing the scheme and netloc + parsed_parent_url = urlparse(url) + parsed_download_url = ParseResult(parsed_parent_url.scheme, + parsed_parent_url.netloc, + parsed_download_url.path, + parsed_download_url.params, + parsed_download_url.query, + parsed_download_url.fragment) + direct_download_url = parsed_download_url.geturl() + direct_download_url = href_from_link_text(direct_download_url, + headers, + 'direct link') + return direct_download_url def win_add_ccx(bitsize, binaries_url, program_name): """Installs ccx on a windows computer""" @@ -276,13 +331,11 @@ def win_add_ccx(bitsize, binaries_url, program_name): headers = {'User-Agent': user_agent} zipfile_regex = '.+CL.+win.+zip/download$' - zipfile_webpage_url = zipfile_by_bitsize(binaries_url, headers, zipfile_regex, - bitsize) + zipfile_webpage_url = zipfile_by_bitsize(binaries_url, headers, + zipfile_regex, bitsize) zipfile_name = zipfile_webpage_url.split('/')[-2] - print(zipfile_webpage_url) zipfile_url = get_direct_url(zipfile_webpage_url, headers) - print(zipfile_url) print('Downloading %s from %s' % (zipfile_name, zipfile_url)) response = requests.get(zipfile_url, stream=True, headers=headers) diff --git a/pycalculix/loads.py b/pycalculix/loads.py index 7011304..4f9776b 100644 --- a/pycalculix/loads.py +++ b/pycalculix/loads.py @@ -108,7 +108,8 @@ def ccx(self): res.append('%s,CENTRIF,%f,0.,0.,0.,0.,1.,0.' % (cname, radsq)) elif self.ltype == 'matl': mat = self.val.name - res.append('*SOLID SECTION,ELSET='+cname+',MATERIAL='+mat) + res.append('*SOLID SECTION,ELSET=%s,MATERIAL=%s' % + (cname, mat)) return res diff --git a/pycalculix/problem.py b/pycalculix/problem.py index 2c1abaf..66574c5 100644 --- a/pycalculix/problem.py +++ b/pycalculix/problem.py @@ -2,8 +2,8 @@ of analysis. """ -import subprocess # used to launch ccx solver import os # used to see if there is a results file +import subprocess # used to launch ccx solver from . import environment from . import base_classes @@ -18,7 +18,7 @@ class Problem(base_classes.Idobj): -- 'struct': structural - fname (str): file prefix for the problem .inp and results files If value is '' it will default to the project name of the FeaModel - + Attributes: fea (FeaModel): parent FeaModel __ptype (str): problem type, options: @@ -233,15 +233,15 @@ def solve(self): inp += matl.ccx() # write all steps and loads - for time in load_dict: - if time == 0: + for time in sorted(load_dict.keys()): + if time == 0.0: # this is for thicknesses and materials for load in load_dict[time]: inp += load.ccx() for surf_interaction in self.fea.surfints: inp += surf_interaction.ccx() for contact in self.fea.contacts: - inp += contact.ccx() + inp += contact.ccx() else: # only write times >= 1 inp.append('*STEP') @@ -274,7 +274,7 @@ def solve(self): # run file runstr = "%s %s" % (environment.CCX, self.fname) print(runstr) - subprocess.call(runstr, shell=True) + subprocess.check_call(runstr, shell=True) print('Solving done!') # select the probem's parts and load the results file diff --git a/pycalculix/results_file.py b/pycalculix/results_file.py index a9a360e..c806c06 100644 --- a/pycalculix/results_file.py +++ b/pycalculix/results_file.py @@ -1,9 +1,10 @@ """This module stores the Results_File class.""" import collections -import re # used to get info from frd file import math # used for metric number conversion import os #need to check if results file exists +import re # used to get info from frd file +import subprocess # used to check ccx version import matplotlib.pyplot as plt import matplotlib.colors as colors @@ -14,6 +15,7 @@ from . import base_classes # needed for RESFIELDS +from . import environment from . import mesh CMAP = 'jet' @@ -1096,6 +1098,27 @@ def _save_ele_stress(self, line, rfstr, time, # each line is an integration point result self.__results[time]['element'][enum]['ipoints'][ipnum] = adict + def check_ccx_version(self, timeout=1): + """Raises an exception of the calculix ccx version is too old""" + runstr = "%s -version; exit 0" % (environment.CCX) + output_str = subprocess.check_output(runstr, + timeout=timeout, + shell=True) + output_str = str(output_str, 'utf-8') + matches = re.findall('\d+\.\d+', output_str) + version_number = matches[-1] + print('Using Calculix ccx version=%s ' + '(trailing characters like the p in 2.8p are omitted)' + % version_number) + major_version, minor_version = [int(f) for f + in version_number.split('.')] + if major_version <= 2 and minor_version <= 8: + raise Exception('Your version of calculix ccx is too old! ' + 'Please update it to version >=2.8 with ' + 'the command:\npycalculix-add-feaprograms') + version = float(output_str.strip().split()[-1]) + # extract version with regex + def __read_frd(self): """ Reads a ccx frd results file which contains nodal results. @@ -1105,6 +1128,9 @@ def __read_frd(self): if not os.path.isfile(fname): print("Error: %s file not found" % fname) return + # frd reading uses formatting from ccx 2.8 or higher so + # throw an exception if our version is too old + self.check_ccx_version(timeout=1) infile = open(fname, 'r') print('Loading nodal results from file: '+fname) mode = None @@ -1178,41 +1204,41 @@ def __read_dat(self): for edict in self.__results[time]['element'].values(): ipoints = edict['ipoints'].values() stress_types = ['Sx', 'Sy', 'Sz', 'Sxy', 'Sxz', 'Syz'] - stresslist_by_stresstype = {} + strslist_by_strstype = collections.defaultdict(list) # set stress values in max, min, avg locations - for stress in stress_types: - stress_vals = [ipt[stress] for ipt in ipoints] + # of non-summary stress components + for stress_type in stress_types: + stress_vals = [ipt[stress_type] for ipt in ipoints] stress_avg = sum(stress_vals)/len(stress_vals) - stress_max, stress_min = max(stress_vals), min(stress_vals) - edict['avg'][stress] = stress_avg - edict['max'][stress] = stress_max - edict['min'][stress] = stress_min - stresslist_by_stresstype[stress] = stress_vals - # for each element, at average calc Seqv, S1, S2, S3 - # for each element, at max and min use the max and min - # of the vals we already found - locs = ['avg', 'max', 'min'] - for loc in locs: - seqv, s_1, s_2, s_3 = None, None, None, None - if loc == 'avg': - vals = [edict[loc][stype] for - stype in stress_types] - seqv = self.__seqv(vals) - [s_1, s_2, s_3] = self.__principals(vals) - elif mode == 'max': - seqv = max(stresslist_by_stresstype['Seqv']) - s_1 = max(stresslist_by_stresstype['S1']) - s_2 = max(stresslist_by_stresstype['S2']) - s_3 = max(stresslist_by_stresstype['S3']) - elif mode == 'min': - seqv = min(stresslist_by_stresstype['Seqv']) - s_1 = min(stresslist_by_stresstype['S1']) - s_2 = min(stresslist_by_stresstype['S2']) - s_3 = min(stresslist_by_stresstype['S3']) - edict[loc]['Seqv'] = seqv - edict[loc]['S1'] = s_1 - edict[loc]['S2'] = s_2 - edict[loc]['S3'] = s_3 + stress_max = max(stress_vals) + stress_min = min(stress_vals) + edict['avg'][stress_type] = stress_avg + edict['max'][stress_type] = stress_max + edict['min'][stress_type] = stress_min + strslist_by_strstype[stress_type] = stress_vals + # for each element, calc Seqv, S1, S2, S3 + # at each integration point + for ipt in ipoints: + stress_vals = [ipt[stress_type] for stress_type + in stress_types] + seqv = self.__seqv(stress_vals) + [s_1, s_2, s_3] = self.__principals(stress_vals) + strslist_by_strstype['Seqv'].append(seqv) + strslist_by_strstype['S1'].append(s_1) + strslist_by_strstype['S2'].append(s_2) + strslist_by_strstype['S3'].append(s_3) + # now at the element level, store the avg, max, and min + # of the Seqv and principal stresses we found at + # integration points + for stress_type in ['Seqv', 'S1', 'S2', 'S3']: + stress_vals = strslist_by_strstype[stress_type] + stress_avg = sum(stress_vals)/len(stress_vals) + stress_max = max(stress_vals) + stress_min = min(stress_vals) + edict['avg'][stress_type] = stress_avg + edict['max'][stress_type] = stress_max + edict['min'][stress_type] = stress_min + print('The following times have been read:') print(self.__steps) diff --git a/pycalculix/version.py b/pycalculix/version.py index e94731c..f8c6ac7 100644 --- a/pycalculix/version.py +++ b/pycalculix/version.py @@ -1 +1 @@ -__version__ = "0.9.4" +__version__ = "0.9.5" diff --git a/setup.py b/setup.py index ed5b756..196e6f3 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name = "pycalculix", - install_requires = ['matplotlib >= 1.3.1', 'numpy', 'dxfgrabber', 'requests'], + install_requires = ['matplotlib >= 1.3.1', 'numpy', 'dxfgrabber >= 0.8.0', 'requests', 'pytest'], python_requires='>=3', version = __version__, description = "Python 3 library to build and solve finite element analysis (FEA) models in Calculix.", diff --git a/tests/test_meshing.py b/tests/test_meshing.py new file mode 100644 index 0000000..45dbc7d --- /dev/null +++ b/tests/test_meshing.py @@ -0,0 +1,86 @@ +import glob +import os +import subprocess +import unittest + +import pycalculix as pyc + +class TestMeshing(unittest.TestCase): + """Test meshing module""" + def tearDown(self): + extenstion_to_del = ['fbd', + 'inp', + 'geo', + 'msh', + 'frd', + 'dat', + 'png', + 'cvg', + 'sta', + 'out'] + local_files = glob.glob('*.*') # files that have extensions + for local_file in local_files: + extension = local_file.split('.')[-1] + if extension in extenstion_to_del: + os.unlink(local_file) + + def most_are_type(self, elements, el_shape, min_percent=90): + min_num_nodes = 3 + if el_shape == 'quad': + min_num_nodes = 4 + # require 90% be asked type + passing_qty = (min_percent/100.0)*len(elements) + type_count = 0 + for element in elements: + if len(element.nodes) % min_num_nodes == 0: + type_count += 1 + print('Percent %ss %f' % + (el_shape, 100*type_count/len(elements))) + if type_count >= passing_qty: + return True + return False + + def test_tri_mesh(self, el_shape='tri'): + proj_name = 'sample' + length = 4.0 # hole diam + + model = pyc.FeaModel(proj_name) + model.set_units('m') # this sets dist units to meters + part = pyc.Part(model) + + part.goto(0, 0) + part.draw_line_ax(length) + part.draw_line_rad(length) + part.draw_line_ax(-length) + part.draw_line_rad(-length) + + # set the element type and mesh database + model.set_eshape(el_shape, 2) + model.mesh(1.0, 'gmsh') # mesh 1.0 fineness, smaller is finer + # model.plot_elements(proj_name+'_elem') # plot part elements + self.assertTrue(self.most_are_type(part.elements, el_shape)) + + def test_quad_mesh(self, el_shape='quad'): + proj_name = 'sample' + length = 4.0 # hole diam + + model = pyc.FeaModel(proj_name) + model.set_units('m') # this sets dist units to meters + part = pyc.Part(model) + + part.goto(0, 0) + part.draw_line_ax(length) + part.draw_line_rad(length) + part.draw_line_ax(-length) + part.draw_line_rad(-length) + + # set the element type and mesh database + model.set_eshape(el_shape, 2) + model.mesh(1.0, 'gmsh') # mesh 1.0 fineness, smaller is finer + # model.plot_elements(proj_name+'_elem') # plot part elements + self.assertTrue(self.most_are_type(part.elements, el_shape)) + + # add tests of fineness here + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_samples.py b/tests/test_samples.py new file mode 100644 index 0000000..a44e3bd --- /dev/null +++ b/tests/test_samples.py @@ -0,0 +1,91 @@ +import glob +import os +import subprocess +import unittest + +import pycalculix as pyc + +class TestExamples(unittest.TestCase): + """Run all examples and make sure no exceptions are thrown""" + def tearDown(self): + extenstion_to_del = ['fbd', + 'inp', + 'geo', + 'msh', + 'frd', + 'dat', + 'png', + 'cvg', + 'sta', + 'out'] + local_files = glob.glob('*.*') # files that have extensions + for local_file in local_files: + extension = local_file.split('.')[-1] + if extension in extenstion_to_del: + os.unlink(local_file) + + def example_tester(self, file_name, args=['-tri', '-nogui']): + command_str = 'python examples/%s %s' % (file_name, + ' '.join(args)) + output = subprocess.check_output( + command_str, stderr=subprocess.STDOUT, shell=True, + timeout=60, + universal_newlines=True) + # this raises subprocess.CalledProcessError if it fails + + def test_compr_rotor(self, file_name='compr-rotor.py'): + self.example_tester(file_name) + + def test_dam_eplot(self, file_name='dam-eplot.py'): + self.example_tester(file_name) + + def test_dam_times(self, file_name='dam-times.py'): + self.example_tester(file_name) + + def test_dam(self, file_name='dam.py'): + self.example_tester(file_name) + + def test_hole_fancy(self, file_name='hole-fancy.py'): + self.example_tester(file_name) + + def test_hole_full(self, file_name='hole-in-plate-full.py'): + self.example_tester(file_name) + + def test_hole_quarter(self, file_name='hole-in-plate-quarter.py'): + self.example_tester(file_name) + + def test_hole_kt(self, file_name='hole-kt-study.py'): + self.example_tester(file_name) + + def test_import_dxf1(self, file_name='import-dxf-1.py'): + self.example_tester(file_name) + + def test_import_dxf2(self, file_name='import-dxf-2.py'): + self.example_tester(file_name) + + def test_line_loop(self, file_name='line-loop.py'): + self.example_tester(file_name) + + def test_multihole(self, file_name='multihole.py'): + self.example_tester(file_name) + + def test_pinned_plate(self, file_name='pinned-plate.py'): + self.example_tester(file_name) + + def test_pipe_crush_elastic(self, + file_name='pipe-crush-elastic.py'): + self.example_tester(file_name) + + def test_rounded_square_ccw(self, + file_name='rounded-square-ccw.py'): + self.example_tester(file_name) + + def test_rounded_square_cw(self, + file_name='rounded-square-cw.py'): + self.example_tester(file_name) + + # note, once tests are written, make sure to add travisci file too + # to ensure that it works on mac and linux + +if __name__ == '__main__': + unittest.main()