mathmaker  0.4(alpha)
mathmaker_dev/core/base_geometry.py
00001 # -*- coding: utf-8 -*-
00002 
00003 # Mathmaker creates automatically maths exercises sheets with their answers
00004 # Copyright 2006-2014 Nicolas Hainaux <nico_h@users.sourceforge.net>
00005 
00006 # This file is part of Mathmaker.
00007 
00008 # Mathmaker is free software; you can redistribute it and/or modify
00009 # it under the terms of the GNU General Public License as published by
00010 # the Free Software Foundation; either version 3 of the License, or
00011 # any later version.
00012 
00013 # Mathmaker is distributed in the hope that it will be useful,
00014 # but WITHOUT ANY WARRANTY; without even the implied warranty of
00015 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00016 # GNU General Public License for more details.
00017 
00018 # You should have received a copy of the GNU General Public License
00019 # along with Mathmaker; if not, write to the Free Software
00020 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
00021 
00022 # ------------------------------------------------------------------------------
00023 # --------------------------------------------------------------------------
00024 # ------------------------------------------------------------------------------
00025 ##
00026 # @package core.base_geometry
00027 # @brief Mathematical elementary geometrical objects.
00028 
00029 import math
00030 import locale
00031 from decimal import *
00032 from base import *
00033 from core.base_calculus import Value
00034 from lib import *
00035 from lib.maths_lib import *
00036 from maintenance import debug
00037 from lib.common import cfg
00038 
00039 markup_choice = cfg.get_value_from_file('MARKUP', 'USE')
00040 
00041 if markup_choice == 'latex':
00042     from lib.common.latex import MARKUP
00043 
00044 if debug.ENABLED:
00045     from lib.common import latex
00046     import machine
00047 
00048 try:
00049     locale.setlocale(locale.LC_ALL, default.LANGUAGE + '.' + default.ENCODING)
00050 except:
00051     locale.setlocale(locale.LC_ALL, '')
00052 
00053 # the mark 'dashed' has been removed from the available list since it may
00054 # produce buggy results sometimes from euktopst
00055 AVAILABLE_ANGLE_MARKS = ['', 'simple', 'double', 'triple', 'right',
00056                          'forth', 'back', 'dotted']
00057 AVAILABLE_SEGMENT_MARKS = []
00058 
00059 # GLOBAL
00060 #expression_begins = True
00061 
00062 
00063 # ------------------------------------------------------------------------------
00064 # --------------------------------------------------------------------------
00065 # ------------------------------------------------------------------------------
00066 ##
00067 # @class Point
00068 # @brief
00069 class Point(Drawable):
00070 
00071 
00072 
00073 
00074 
00075     # --------------------------------------------------------------------------
00076     ##
00077     #   @brief Constructor.
00078     #   @param arg : [String, (nb,nb)]|Point
00079     #   Types details :
00080     #   -
00081     #   @param options
00082     #   Options details :
00083     #   -
00084     #   @warning Might raise...
00085     def __init__(self, arg, **options):
00086         if not (type(arg) == list or isinstance(arg, Point)):
00087             raise error.WrongArgument(' list|Point ', str(type(arg)))
00088 
00089         elif type(arg) == list:
00090             if not len(arg) == 2:
00091                 raise error.WrongArgument(' a list of length 2 ',
00092                                           ' a list of length ' \
00093                                           + str(len(arg))
00094                                           )
00095 
00096             if not type(arg[0]) == str:
00097                 raise error.WrongArgument(' a str ', str(type(arg[0])))
00098 
00099             if not (type(arg[1]) == tuple \
00100                 and len(arg[1]) == 2 \
00101                 and is_.a_number(arg[1][0]) \
00102                 and is_.a_number(arg[1][1])):
00103             #___
00104                 raise error.WrongArgument(' (x, y) ', str(arg))
00105 
00106             self._name = arg[0]
00107             self._x = Decimal(arg[1][0])
00108             self._y = Decimal(arg[1][1])
00109 
00110         else:
00111             self._name = arg.name
00112             self._x = arg.x
00113             self._y = arg.y
00114 
00115 
00116 
00117 
00118     # --------------------------------------------------------------------------
00119     ##
00120     #   @brief Returns the exact abscissa of the Point
00121     def get_x_exact(self):
00122         return self._x
00123     # --------------------------------------------------------------------------
00124     x_exact = property(get_x_exact, doc = "Abscissa of the Point (exact)")
00125 
00126 
00127 
00128 
00129 
00130     # --------------------------------------------------------------------------
00131     ##
00132     #   @brief Returns the abscissa of the Point, rounded up to the tenth
00133     def get_x(self):
00134         return round(Decimal(str(self._x)),
00135                      Decimal('0.01'),
00136                      rounding=ROUND_HALF_UP
00137                     )
00138     # --------------------------------------------------------------------------
00139     x = property(get_x, doc = "Abscissa of the Point (rounded)")
00140 
00141 
00142 
00143 
00144 
00145     # --------------------------------------------------------------------------
00146     ##
00147     #   @brief Sets the abscissa of the Point
00148     def set_x(self, arg):
00149         if not is_.a_number(arg):
00150             raise error.WrongArgument(' a number ', str(arg))
00151 
00152         self._x = arg
00153 
00154 
00155 
00156 
00157     # --------------------------------------------------------------------------
00158     ##
00159     #   @brief Returns the exact ordinate of the Point
00160     def get_y_exact(self):
00161         return self._y
00162     # --------------------------------------------------------------------------
00163     y_exact = property(get_y_exact, doc = "Ordinate of the Point (rounded)")
00164 
00165 
00166 
00167 
00168 
00169     # --------------------------------------------------------------------------
00170     ##
00171     #   @brief Returns the ordinate of the Point, rounded up to the tenth
00172     def get_y(self):
00173         return round(Decimal(str(self._y)),
00174                      Decimal('0.01'),
00175                      rounding=ROUND_HALF_UP
00176                     )
00177     # --------------------------------------------------------------------------
00178     y = property(get_y, doc = "Ordinate of the Point (exact)")
00179 
00180 
00181 
00182 
00183 
00184     # --------------------------------------------------------------------------
00185     ##
00186     #   @brief Sets the ordinate of the Point
00187     def set_y(self, arg):
00188         if not is_.a_number(arg):
00189             raise error.WrongArgument(' a number ', str(arg))
00190 
00191         self._y = arg
00192 
00193 
00194 
00195 
00196 
00197     # --------------------------------------------------------------------------
00198     ##
00199     #   @brief Returns the name of the Point
00200     def get_name(self):
00201         return self._name
00202     # --------------------------------------------------------------------------
00203     name = property(get_name,
00204                     doc = "Name of the Point")
00205 
00206 
00207 
00208 
00209 
00210     # --------------------------------------------------------------------------
00211     ##
00212     #   @brief Sets the name of the Point
00213     def set_name(self, arg):
00214         if not is_.a_string(arg):
00215             raise error.WrongArgument(' a string ', str(arg))
00216 
00217         self._name = arg
00218 
00219 
00220 
00221 
00222 
00223     # --------------------------------------------------------------------------
00224     ##
00225     #   @brief Returns a new Point after rotation of self
00226     def rotate(self, center, angle, **options):
00227         if not isinstance(center, Point):
00228             raise error.WrongArgument(' a Point ', str(type(center)))
00229 
00230         if not is_.a_number(angle):
00231             raise error.WrongArgument(' a number ', str(type(angle)))
00232 
00233         delta_x = self.x_exact - center.x_exact
00234         delta_y = self.y_exact - center.y_exact
00235 
00236         rx = delta_x*Decimal(str(math.cos(deg_to_rad(angle)))) \
00237              - delta_y*Decimal(str(math.sin(deg_to_rad(angle)))) \
00238              + center.x_exact
00239         ry = delta_x*Decimal(str(math.sin(deg_to_rad(angle)))) \
00240              + delta_y*Decimal(str(math.cos(deg_to_rad(angle)))) \
00241              + center.y_exact
00242 
00243         new_name = self.name + "'"
00244 
00245         if 'keep_name' in options and options['keep_name'] == True:
00246             new_name = self.name
00247 
00248         elif 'new_name' in options and type(options['new_name']) == str:
00249             new_name = options['new_name']
00250 
00251         return Point([new_name, (rx, ry)])
00252 
00253 
00254 
00255 
00256 
00257 
00258 
00259 
00260 
00261 
00262 
00263 # ------------------------------------------------------------------------------
00264 # --------------------------------------------------------------------------
00265 # ------------------------------------------------------------------------------
00266 ##
00267 # @class Segment
00268 # @brief
00269 class Segment(Drawable):
00270 
00271 
00272 
00273 
00274 
00275     # --------------------------------------------------------------------------
00276     ##
00277     #   @brief Constructor.
00278     #   @param arg (Point, Point)
00279     #   Types details :
00280     #   -
00281     #   @param options
00282     #   Options details :
00283     #   -
00284     #   @warning Might raise...
00285     def __init__(self, arg, **options):
00286         if not (type(arg) == tuple or isinstance(arg, Segment)):
00287             raise error.WrongArgument(' tuple|Segment ', str(type(arg)))
00288 
00289         elif type(arg) == tuple:
00290             if not (isinstance(arg[0], Point) and isinstance(arg[1], Point)):
00291             #___
00292                 raise error.WrongArgument(' (Point, Point) ', str(arg))
00293 
00294             self._points = (arg[0].clone(), arg[1].clone())
00295 
00296             self._label  = None
00297 
00298             if 'label' in options and type(options['label']) == str:
00299                 self._label = options['label']
00300 
00301         else:
00302             self._points = (arg.points[0].clone(),
00303                             arg.points[1].clone()
00304                            )
00305             self._label = arg.label
00306 
00307         self._mark = None
00308 
00309 
00310 
00311 
00312 
00313     # --------------------------------------------------------------------------
00314     ##
00315     #   @brief Returns the two points
00316     def get_points(self):
00317         return self._points
00318     # --------------------------------------------------------------------------
00319     points = property(get_points,
00320                       doc = "The couple of points ending the segment")
00321 
00322 
00323 
00324 
00325 
00326     # --------------------------------------------------------------------------
00327     ##
00328     #   @brief Sets the points of the Segment (is this useful at all ?)
00329     def set_point(self, nb, arg):
00330         if not is_.a_number(nb):
00331             raise error.WrongArgument(' a number ', str(nb))
00332         if not isinstance(arg, Point):
00333             raise error.WrongArgument(' a Point ', str(arg))
00334 
00335 
00336         self._points[nb] = arg.clone()
00337 
00338 
00339 
00340 
00341     # --------------------------------------------------------------------------
00342     ##
00343     #   @brief Returns the name of the Segment
00344     def get_name(self):
00345         return "[" + self.points[0].name + self.points[1].name + "]"
00346     # --------------------------------------------------------------------------
00347     name = property(get_name,
00348                     doc = "Name of the Segment")
00349 
00350 
00351 
00352 
00353 
00354     # --------------------------------------------------------------------------
00355     ##
00356     #   @brief Returns the length name of the Segment
00357     def get_length_name(self):
00358         return self.points[0].name + self.points[1].name
00359     # --------------------------------------------------------------------------
00360     length_name = property(get_length_name,
00361                            doc = "Length's name of the Segment")
00362 
00363 
00364 
00365 
00366 
00367     # --------------------------------------------------------------------------
00368     ##
00369     #   @brief Sets the label of the Segment
00370     def set_label(self, arg):
00371         if not type(arg) == Value:
00372             raise error.WrongArgument(' Value ', str(type(arg)))
00373 
00374         self._label = arg
00375 
00376 
00377 
00378 
00379 
00380     # --------------------------------------------------------------------------
00381     ##
00382     #   @brief Returns the label of the Segment
00383     def get_label(self):
00384         return self._label
00385     # --------------------------------------------------------------------------
00386     label = property(get_label,
00387                      doc = "Label of the Segment")
00388 
00389 
00390 
00391 
00392 
00393     # --------------------------------------------------------------------------
00394     ##
00395     #   @brief Sets the mark of the Segment
00396     def set_mark(self, arg):
00397         if not type(arg) == str:
00398             raise error.WrongArgument(' str ', str(type(arg)))
00399 
00400         if not arg in AVAILABLE_SEGMENT_MARKS:
00401             raise error.WrongArgument(arg, 'a string from this list : ' \
00402                                       + str(AVAILABLE_SEGMENT_MARKS))
00403 
00404         self._mark = arg
00405 
00406 
00407 
00408 
00409 
00410     # --------------------------------------------------------------------------
00411     ##
00412     #   @brief Returns the mark of the Segment
00413     def get_mark(self):
00414         return self._mark
00415     # --------------------------------------------------------------------------
00416     mark = property(get_mark,
00417                     doc = "Mark of the Segment")
00418 
00419 
00420 
00421 
00422 
00423     # --------------------------------------------------------------------------
00424     ##
00425     #   @brief Returns the length of the Segment
00426     def get_length(self):
00427         x_delta = self.points[0].x - self.points[1].x
00428         y_delta = self.points[0].y - self.points[1].y
00429         return math.hypot(x_delta, y_delta)
00430 
00431     # --------------------------------------------------------------------------
00432     length = property(get_length,
00433                       doc = "Name of the Segment")
00434 
00435 
00436 
00437 
00438 
00439 
00440 # ------------------------------------------------------------------------------
00441 # --------------------------------------------------------------------------
00442 # ------------------------------------------------------------------------------
00443 ##
00444 # @class Ray
00445 # @brief
00446 class Ray(Drawable):
00447 
00448 
00449 
00450 
00451 
00452     # --------------------------------------------------------------------------
00453     ##
00454     #   @brief Constructor.
00455     #   @param arg : (Point, Point)
00456     #   @param options : label
00457     #   Options details :
00458     #   @warning Might raise...
00459     def __init__(self, arg, **options):
00460         self._point0 = None # the initial point
00461         self._point1 = None
00462         self._name = None
00463 
00464         if isinstance(arg, tuple) and len(arg) == 2 \
00465             and isinstance(arg[0], Point) \
00466             and isinstance(arg[1], Point):
00467         #___
00468             self._point0 = arg[0].clone()
00469             self._point1 = arg[1].clone()
00470             self._name = MARKUP['opening_square_bracket']
00471             self._name += arg[0].name + arg[1].name
00472             self._name += MARKUP['closing_bracket']
00473 
00474 
00475 
00476 
00477 
00478 # ------------------------------------------------------------------------------
00479 # --------------------------------------------------------------------------
00480 # ------------------------------------------------------------------------------
00481 ##
00482 # @class Angle
00483 # @brief
00484 class Angle(Drawable):
00485 
00486 
00487 
00488 
00489 
00490     # --------------------------------------------------------------------------
00491     ##
00492     #   @brief Constructor.
00493     #   @param arg : (Point, Point, Point)
00494     #   @param options : label
00495     #   Options details :
00496     #   @warning Might raise...
00497     def __init__(self, arg, **options):
00498         self._ray0 = None
00499         self._ray1 = None
00500         self._points = None
00501         self._measure = None
00502         self._mark = ""
00503         self._label = Value("")
00504         self._name = None
00505 
00506         if isinstance(arg, tuple) and len(arg) == 3 \
00507             and isinstance(arg[0], Point) \
00508             and isinstance(arg[1], Point) \
00509             and isinstance(arg[2], Point):
00510         #___
00511             self._ray0 = Ray((arg[1], arg[0]))
00512             self._ray1 = Ray((arg[1], arg[2]))
00513             self._points = [arg[0].clone(),
00514                             arg[1].clone(),
00515                             arg[2].clone()]
00516 
00517             # Let's determine the measure of the angle :
00518             aux_side0 = Segment((self._points[0], self._points[1]))
00519             aux_side1 = Segment((self._points[1], self._points[2]))
00520             aux_side2 = Segment((self._points[2], self._points[0]))
00521             aux_num = aux_side0.length * aux_side0.length \
00522                     + aux_side1.length * aux_side1.length \
00523                     - aux_side2.length * aux_side2.length
00524             aux_denom = 2 * aux_side0.length * aux_side1.length
00525             aux_cos = aux_num / aux_denom
00526             self._measure = Decimal(str(math.degrees(math.acos(aux_cos))))
00527 
00528             if 'label' in options and type(options['label']) == str:
00529                 self._label = options['label']
00530 
00531             if 'mark' in options and type(options['mark']) == str:
00532                 self._mark = options['mark']
00533 
00534             self._name = MARKUP['opening_widehat']
00535             self._name += arg[0].name + arg[1].name + arg[2].name
00536             self._name += MARKUP['closing_widehat']
00537 
00538         else:
00539             raise error.WrongArgument(' (Point, Point, Point) ', str(type(arg)))
00540 
00541         self._label_display_angle = round(Decimal(str(self._measure)),
00542                                           Decimal('0.1'),
00543                                           rounding=ROUND_HALF_UP
00544                                          ) / 2
00545 
00546 
00547 
00548 
00549 
00550     # --------------------------------------------------------------------------
00551     ##
00552     #   @brief Returns the name of the angle
00553     def get_name(self):
00554         return self._name
00555     # --------------------------------------------------------------------------
00556     name = property(get_name,
00557                     doc = "Name of the angle")
00558 
00559 
00560 
00561 
00562 
00563     # --------------------------------------------------------------------------
00564     ##
00565     #   @brief Returns the measure of the angle
00566     def get_measure(self):
00567         return self._measure
00568     # --------------------------------------------------------------------------
00569     measure = property(get_measure,
00570                        doc = "Measure of the angle")
00571 
00572 
00573 
00574 
00575 
00576     # --------------------------------------------------------------------------
00577     ##
00578     #   @brief Returns the point0 of the angle
00579     def get_point0(self):
00580         return self._points[0]
00581     # --------------------------------------------------------------------------
00582     point0 = property(get_point0,
00583                       doc = "Point0 of the angle")
00584 
00585 
00586 
00587 
00588     # --------------------------------------------------------------------------
00589     ##
00590     #   @brief Returns the vertex of the angle, as a Point
00591     def get_point1(self):
00592         return self._points[1]
00593     # --------------------------------------------------------------------------
00594     point1 = property(get_point1,
00595                       doc = "Vertex of the angle")
00596     vertex = property(get_point1,
00597                       doc = "Vertex of the angle")
00598 
00599 
00600 
00601     # --------------------------------------------------------------------------
00602     ##
00603     #   @brief Returns the point2 of the angle
00604     def get_point2(self):
00605         return self._points[2]
00606     # --------------------------------------------------------------------------
00607     point2 = property(get_point2,
00608                       doc = "Point2 of the angle")
00609 
00610 
00611 
00612     # --------------------------------------------------------------------------
00613     ##
00614     #   @brief Returns the label of the angle
00615     def get_label(self):
00616         return self._label
00617     # --------------------------------------------------------------------------
00618     label = property(get_label,
00619                      doc = "Label of the angle")
00620 
00621 
00622 
00623 
00624 
00625     # --------------------------------------------------------------------------
00626     ##
00627     #   @brief Returns the angle (for display) of label's angle
00628     def get_label_display_angle(self):
00629         return self._label_display_angle
00630     # --------------------------------------------------------------------------
00631     label_display_angle = property(get_label_display_angle,
00632                                 doc = "Display angle of the label of the angle")
00633 
00634 
00635 
00636 
00637 
00638     # --------------------------------------------------------------------------
00639     ##
00640     #   @brief Returns the mark of the angle
00641     def get_mark(self):
00642         return self._mark
00643     # --------------------------------------------------------------------------
00644     mark = property(get_mark,
00645                           doc = "Mark of the angle")
00646 
00647 
00648 
00649 
00650 
00651     # --------------------------------------------------------------------------
00652     ##
00653     #   @brief Sets the label of the angle
00654     def set_label(self, arg):
00655         if type(arg) == str or isinstance(arg, Value):
00656             self._label = Value(arg)
00657 
00658         else:
00659             raise error.WrongArgument(arg, 'str or Value')
00660 
00661 
00662 
00663 
00664 
00665     # --------------------------------------------------------------------------
00666     ##
00667     #   @brief Sets the angle (for display) of label's angle
00668     def set_label_display_angle(self, arg):
00669         if not is_.a_number(arg):
00670             raise error.WrongArgument(arg, ' a number ')
00671         else:
00672             self._label_display_angle = round(Decimal(str(arg)),
00673                                               Decimal('0.1'),
00674                                               rounding=ROUND_HALF_UP
00675                                              )
00676 
00677 
00678 
00679 
00680 
00681     # --------------------------------------------------------------------------
00682     ##
00683     #   @brief Sets the mark of the angle
00684     def set_mark(self, arg):
00685         if type(arg) == str:
00686             if arg in AVAILABLE_ANGLE_MARKS:
00687                 self._mark = arg
00688             else:
00689                 raise error.WrongArgument(arg, 'a string from this list : ' \
00690                                                + str(AVAILABLE_ANGLE_MARKS))
00691 
00692         else:
00693             raise error.WrongArgument(arg, 'str')
00694 
00695 
00696 
00697 
00698 
00699 
00700 
00701 
00702