Source code for basd.cad.cad_base

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2010 - 2024, Fraunhofer-Gesellschaft zur Foerderung der angewandten Forschung e.V.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice, this
#    list of conditions and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# 3. Neither the name of the copyright holder nor the names of its
#    contributors may be used to endorse or promote products derived from
#    this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""basd_cad_block contains base functionalities to build CAD object made out of other
CAD objects. For example a battery module is made out of several cell blocks

classes:
    ConfigError
    BlockBasdCAD

functions:
    build_row
    combine
    create_case
    get_center
    get_dimensions
"""

from typing import Optional

import cadquery as cq

from .cad_system_definition import InnerPadding, OuterPadding, RowOffset


[docs] class ConfigError(Exception): """ConfigError as self defined Exception used configuration errors"""
[docs] class BaseCAD: # pylint: disable=too-few-public-methods """BaseCAD class is the base class for all other classes in the CAD tool :ivar x_pos: position in x-direction :ivar y_pos: position in y-direction :ivar z_pos: position in z-direction """
[docs] def __init__(self, x_pos: float, y_pos: float, z_pos: float): self.x_pos: float = x_pos self.y_pos: float = y_pos self.z_pos: float = z_pos
[docs] class CADBlock(BaseCAD): """CADBlock is the base class for the block build (cell_block, module and string) :ivar x_pos: x position of the block :ivar y_pos: y position of the block :ivar z_pos: z position of the block :ivar x_dir: number of elements in x direction :ivar y_dir: number of elements in y direction :ivar z_dir: number of elements in z direction :ivar offset: row offset definition as lists in a list , :ivar padding: space to next objects in x,y,z direction """
[docs] def __init__( # pylint: disable=too-many-arguments self, x_pos: float, y_pos: float, z_pos: float, x_dir: int, y_dir: int, z_dir: int, row_offset: RowOffset, inner_padding: InnerPadding, outer_padding: OuterPadding, ): super().__init__(x_pos, y_pos, z_pos) self.x_dir = x_dir self.y_dir = y_dir self.z_dir = z_dir self.row_offset = row_offset self.inner_padding = inner_padding self.outer_padding = outer_padding
[docs] def create_object( self, block_shapes: list[cq.Workplane], alternate=False, ) -> cq.Workplane: """create_object adds a certain number of rows made out of block_shapes in x and z direction depending on the values in self.number_of_blocks_in_row to the handle :param handle: the workplane at which the block_shapes should be added :param block_shapes: list of cq.workplanes containing a shape object :param alternate: is needed to realize a series connection with cell blocks, which need to be rotated alternately in a series connection :return: passed handle with the new block_shapes added to the stack """ # row consist out of two element each representing one possible row configuration # one row element consist out of several CAD components translated in y-direction # the alternating arrangement of the CAD components is the second possible # representation of a row row = [] # rows are build by combining the row elements and they represent one layer layer = [] # layers are build by combining the rows elements and they represent the result layers = [] if self.y_dir == 1: row.append(block_shapes[0]) row.append(block_shapes[1]) else: # build "standard" row row.append( CADBlock.build_row( block_shapes, 1, self.inner_padding.y, self.row_offset.first, iterations=self.y_dir, ) ) # build reversed "alternating" row compared to first row representation if alternate: block_shapes.reverse() row.append( CADBlock.build_row( block_shapes, 1, self.inner_padding.y, self.row_offset.second, iterations=self.y_dir, ) ) # combine the two representations of the row to one layer # if the row should not be alternated then only the first row representation # is used to build the one layer for i in range(1, self.x_dir + 1): if alternate and i % 2 == 0: layer.append(row[1]) else: layer.append(row[0]) if len(layer) > 1: layer = CADBlock.build_row( layer, 0, self.inner_padding.x, offset=0, ) else: layer = layer[0] layers = [layer] * self.z_dir # if statement needed in case block element is two dimensional, because for a # two dimensional case self.inner_padding has not three elements if len(layers) > 1: result = CADBlock.build_row( layers, 2, self.inner_padding.z, offset=0, ) else: result = layers[0] return result
[docs] @staticmethod def build_row( # pylint: disable=too-many-arguments components: list[cq.Workplane], direction: int, padding: float, offset: float, iterations: Optional[int] = None, handle: Optional[cq.Workplane] = None, ) -> cq.Workplane: """build_row builds a row by arranging the passed components alternately in a certain direction :ivar components: list of cq.Workplane object with shapes in it, which should be used to build the row :ivar direction: 1 as x direction, 2 as y direction and 3 as z direction :ivar iterations: number of components placed in the row :ivar padding: space between each component in mm :ivar offset: offset at the beginning of the row :return: new workplane with all components added to the stack """ def index_comp(ind: int, components: list): """used to alternate the component index :param ind: index of the component :param components: list of cq.Workplane object with shapes in it, which should be used to build the row """ return ind % len(components) if handle is None: handle = cq.Workplane() if iterations is None: iterations = len(components) translation_vector = [0, 0, 0] component_dimensions = CADBlock.get_dimensions(components[0])[direction] for i in range(iterations): component = components[index_comp(i, components)] translation_vector[direction] = offset + i * ( component_dimensions + padding ) handle.add(component.translate(translation_vector)) return handle
[docs] @staticmethod def get_dimensions(components: cq.Workplane, center=False) -> tuple: """Return the dimensions of the passed components or if needed the center of the components :param components: workplane with a shape object on its stack :return: width, length and height of the component """ for i, comp in enumerate(components.vals()): if i == 0: bounding = comp.BoundingBox() else: bounding = bounding.add(comp.BoundingBox()) if center: return bounding.center return bounding.xlen, bounding.ylen, bounding.zlen
[docs] @staticmethod def add_box( handle: cq.Workplane, padding: tuple = None, add: tuple = None, thickness: float = 0.001, ) -> cq.Workplane: """adds the "bounding" box surrounding the elements in the passed workplane :param handle: the workplane at which the box should be added :param padding: padding between box and underlying shapes :param thickness: thickness of the box edges, defaults to 0.001 :return: workplanes with hollowed box around other shapes """ if padding is None: padding = [0, 0, 0] if add is None: add = [0, 0, 0] # get dimensions of the all shapes in the workplane shapes_dim = CADBlock.get_dimensions(handle) # get center of all shapes in the workplane center_of_shapes = CADBlock.get_dimensions(handle, center=True) final_dim = [ shapes_dim[i] + padding[i] + add[i] for i in range(len(shapes_dim)) ] box = cq.Workplane("XY").box(*final_dim) # cut out everything except the edges box = ( box.faces(">X") .center(0, 0) .workplane() .rect(final_dim[1] - thickness, final_dim[2] - thickness) .cutThruAll() ) box = ( box.faces(">Y") .center(0, 0) .workplane() .rect(final_dim[0] - thickness, final_dim[2] - thickness) .cutThruAll() ) box = ( box.faces(">Z") .center(0, 0) .workplane() .rect(final_dim[0] - thickness, final_dim[1] - thickness) .cutThruAll() ) # move hollowed box to center of other shapes box = box.translate(center_of_shapes).translate([x / 2 for x in add]) return handle.add(box)