def __init__(self, geardata, flankmods=None, formcoords=None):
        """
        Initialization of GearWheel-object.
        All parameters in accordance to DIN 3960 and DIN 3967.

        INPUT parameters:
        z        : number of teeth (numeric, integer)
        m_n      : normal module (numeric, positive)
        d        : pitch diameter (numeric)
                   two of the three parameters z, m_n, d, must be supplied
        b        : tooth width (numeric, positive)
        d_f      : root circle diameter (numeric)
                   optional - calculated if not supplied
        d_a      : tip diameter (numeric)
                   optional - calculated if not supplied
        d_Ff     : root form diameter (numeric)
                   optional - will be estimated if not supplied
        d_Fa     : tip form diameter (numeric)
                   optional - set equal da if not supplied (no chamfer)
        rho_f    : fillet radius (numeric)
                   optional - set equal 0.38*mn if not supplied
        x        : addendum modification factor (numeric)
                   optional - set equal 0.0 if not supplied
        alpha_n  : pressure angle (numeric, positive)[degrees]
                   optional - set equal 20.0 if not supplied
        beta     : helix angle (numeric)[degrees]
                   optional - set equal 0.0 if not supplied
        a        : addendum (numeric)
                   optional - no estimation
        c        : tip clearance (numeric, positive, 0.1...0.3*mn)
                   optional - set equal 0.25*mn if not supplied
        alpha_wt : service pressure angle (numeric, positive)[degrees]
                   optional - calculated from z_2 or d_w
        d_w      : service pitch diameter (numeric)
                   optional - calculated from alpha_wt or z_2
        h_k      : radial size of tip chamfer (numeric)
                   optional - set equal d_a-d_Fa or 0.0 if not supplied
        s_aK     : remaining tooth thickness at tip, chord-length (numeric)
                   optional - set equal s_a-2*h_k if not supplied
        z_2      : number of teeth of counter gear (numeric, integer)
                   optional - calculated from alpha_wt or d_w
        d_s      : shaft diameter, inner gear wheel diameter (numeric)
                   optional - set equal 0.0 if not supplied
        A_s      : tooth thickness allowance in normal cross-section (numeric, negative)
                   optional - set equal 0.0 if not supplied

            All input parameters above are arranged in a dictionary. The keys are
            the names of the parameters as listed above.

            formcoords : 2D cartesian coordinates of points on the
                         toothflank, describing a half tooth (TColgp_Array1OfPnt2d, pythonOCC)

            There are several possibilities for defining a complete gearwheel:
            1) z, m_n, b, (beta), formcoords
            2) z, m_n, b, (beta), d_f, d_a, d_Ff, d_Fa, rho_f
            3) z, m_n, b, (beta), alpha_n, alpha_wt, x, a, rho_f
            4) z, m_n, b, (beta), alpha_n, z_2, x, a, rho_f
            Some parameters can be left out, but the result might differ
            from your real gear. Missing parameters are estimated if
            possible. The helix angle beta doesn't have to be supplied
            for a spur gear.
            The constructor does not check for unit consistency. The user is
            responsible for supplying all values with consistent units.
            """
        super(CylindricalGearWheel, self).__init__(geardata)
        self.data = deepcopy(geardata)
        self.modifications = deepcopy(flankmods)

        # form coordinates: value check (at least two points for defining a
        # tooth form (straight flanks) and two coordinates per point)
        if formcoords:
            self.data.update(self._analyze_formcoords())

        # module: value check
        if 'm_n' in self.data and not self.data.get('m_n') >= 0:
            raise ValueError('module non-positive')

        if 'beta' not in self.data:
            self.data.update({'beta': self._beta_default})
        self.data.update({'alpha_t': degrees(
            atan(tan(radians(self.data.get('alpha_n'))) / cos(radians(self.data.get('beta')))))})
        self.data.update({'s_p': (pi * self.data['m_n'] / 2) + 2 * self.data['m_n'] * self.data['x'] * tan(
            radians(self.data['alpha_n']))})
        if 'tau' in self.data and 'z' not in self.data:
            self.data.update({'z': int(2 * pi / self.data.get('tau'))})
        if 'z' in self.data and 'm_n' in self.data:
            self.data.update(
                {'d': self.data.get('m_n') * self.data.get('z') / cos(radians(self.data.get('beta')))})
        elif 'z' in self.data and 'd' in self.data:
            self.data.update(
                {'m_n': self.data.get('d') * cos(radians(self.data.get('beta'))) / self.data.get('z')})
        elif 'm_n' in self.data and 'd' in self.data:
            self.data.update({
                'z': int(self.data.get('d') * cos(radians(self.data.get('beta'))) / self.data.get('m_n'))})
        else:
            raise AttributeError('insufficient data supplied')

        if 'tau' not in self.data:
            self.data.update({'tau': degrees(2 * pi / self.data.get('z'))})

        isexternal = sign(self.data.get('z'))
        if not sign(self.data.get('d')) == isexternal:
            raise ValueError('sign of pitch diameter')

        self.data.update({'m_t': self.data.get('m_n') / cos(radians(self.data.get('beta')))})
        if 'alpha_n' in self.data:
            if self.data.get('alpha_n') < 0:
                raise ValueError('pitch angle non-positive')
        else:
            self.data.update({'alpha_n': self._alpha_n_default})

        if 'x' not in self.data:
            self.data.update({'x': self._x_default})

        if 'A_s' not in self.data:
            self.data.update({'A_s': self._A_s_default})
        # tooth thickness allowance: value check
        else:
            if not self.data.get('A_s') <= 0:
                raise ValueError('tooth thickness allowance positive')

        self.data.update({'x_E': self.data.get('x') + self.data.get('A_s') / 2 / tan(
            radians(self.data.get('alpha_n'))) / self.data.get('m_n')})
        if 'd_w' in self.data and 'alpha_wt' not in self.data:
            if not sign(self.data.get('d_w')) == isexternal:
                raise ValueError('sign of service pitch diameter')
            self.data.update({'alpha_wt': degrees(acos(
                self.data.get('d') / self.data.get('d_w') * cos(radians(self.data.get('alpha_t')))))})

        if 'alpha_wt' in self.data and 'd_w' not in self.data:
            self.data.update({'d_w': self.data.get('d') * cos(radians(self.data.get('alpha_t'))) / cos(
                radians(self.data.get('alpha_wt')))})

        self.data.update({'d_b': self.data.get('d') * cos(radians(self.data.get('alpha_t')))})
        if formcoords:
            self.data.update(self._analyze_formcoords())
        if not formcoords:
            # tip clearance: value check, set to default if not supplied
            if 'c' in self.data:
                if self.data.get('c') < 0.1 * self.data.get('m_n') or self.data.get('c') > 0.3 * self.data.get(
                        'm_n'):
                    raise ValueError('tip clearance out of bounds')
            else:
                self.data.update({'c': self._c_default * self.data.get('m_n')})

            # fillet radius: value check, set to default if not supplied
            if 'rho_f' not in self.data:
                self.data.update({'rho_f': self._rho_f_default * self.data.get('m_n')})
            else:
                if self.data.get('rho_f') < 0:
                    raise ValueError('fillet radius negative')

            # CAUTION: THE FOLLOWING SECTION OF CODE WILL BE REMOVED IN FUTURE RELEASES!
            # tool fillet radius: value check
            if 'rho_fP' in self.data:
                if self.data.get('rho_fP') < 0:
                    raise ValueError('tool fillet radius negative')
                if not self.data.get('beta') == 0:
                    raise ValueError('fillet trochoid cannot be generated for helical gears')
            # END OF CODE SECTION TO BE REMOVED

            # calculate tip height modification factor if possible (else set to default)
            # various attempts are made
            if 'a' in self.data and 'k' not in self.data:
                self.data.update(
                    {'a_d': self.data.get('m_t') * (self.data.get('z') + self.data.get('z_2')) / 2})
                self.data.update({'k': (self.data.get('a') - self.data.get('a_d')) / self.data.get('m_n') - (
                    self.data.get('x') + self.data.get('x_2'))})
            else:
                self.data.update({'k': self._k_default})

            # root circle diameter: value check, calculate if not supplied
            if 'd_f' in self.data:
                if 'd_f' in self.data > 'd' in self.data:
                    raise ValueError('root circle diameter greater than pitch diameter')
                if not sign(self.data.get('d_f')) == isexternal:
                    raise ValueError('sign of root circle diameter')
            else:
                self.data.update({
                    'd_f': self.data.get('d') + 2 * self.data.get('x_E') * self.data.get('m_n') - 2 * (
                        self.data.get('m_n') + self.data.get('c'))})

            # tip diameter: value check, calculate if not supplied
            if 'd_a' in self.data:
                # if self.data.get('d_a')<self.data.get('d'):
                # raise ValueError, 'tip diameter less than pitch diameter'
                if not sign(self.data.get('d_a')) == isexternal:
                    raise ValueError('sign of tip diameter')
            else:
                self.data.update({
                    'd_a': self.data.get('d') + 2 * self.data.get('x') * self.data.get('m_n') + 2 * self.data.get(
                        'm_n') + 2 * self.data.get('k') * self.data.get('m_n')})

            # radial value of tip chamfer: value check, calculate or set to default
            # if not supplied
            if 'h_k' in self.data:
                if self.data.get('h_k') < 0:
                    raise ValueError('value of tip chamfer negative')
            elif 'd_Fa' in self.data:
                self.data.update({'h_k': abs(self.data.get('d_a') - self.data.get('d_Fa')) / 2})
            else:
                self.data.update({'h_k': self._h_k_default})

            # remaining tooth thickness: value check, set to default if not supplied
            s_a, d_ac = self._tooth_thickness(self.data.get('d_a'))
            if 's_aK' not in self.data:
                self.data.update({'s_aK': s_a - 2 * self.data.get('h_k')})
            if self.data.get('s_aK') < 0:
                raise ValueError('remaining tooth thickness at tip negative')
            if self.data.get('s_aK') > s_a:
                raise ValueError('remaining tip tooth thickness greater than tooth thickness')

            if 'd_Ff' in self.data:
                if self.data.get('d_Ff') > self.data.get('d'):
                    raise ValueError('root form diameter greater than pitch diameter')
                if self.data.get('d_Ff') < self.data.get('d_f'):
                    raise ValueError('root form diameter less than root circle diameter')
                if not sign(self.data.get('d_Ff')) == isexternal:
                    raise ValueError('sign of root form diameter')

            # tip form diameter: value check
            if 'd_Fa' in self.data:
                if self.data.get('d_Fa') < self.data.get('d'):
                    raise ValueError('tip form diameter less than pitch diameter')
                if self.data.get('d_Fa') > self.data.get('d_a'):
                    raise ValueError('tip form diameter greater than tip diameter')
                if not sign(self.data.get('d_Fa')) == isexternal:
                    raise ValueError('sign of tip form diameter')
            else:
                self.data.update({'d_Fa': self.data.get('d_a') - 2 * self.data.get('h_k')})

        if 'd_s' not in self.data:
            self.data.update({'d_s': self._d_s_default})
        if abs(self.data.get('d_s')) > self._tol_default:
            if not sign(self.data.get('d_s')) == isexternal:
                raise ValueError('sign of shaft diameter')
            if not self.data.get('d_s') < self.data.get('d_f'):
                raise ValueError('shaft diameter greater than root circle diameter')

        if not self.formcoords:
            self._make_form_coords()
        else:
            self.formcoords = self._make_unique(self.formcoords)