from ocp_vscode import * from build123d import * from math import tan, sin, cos, sqrt, pi, radians def CounterVal(val): if val >= 0: return f"+{val}" else: return f"{val}" def NumStr(val): if val < 0: return f"n{val}" else: return f"{val}" class Parallelogram(BaseSketchObject): def __init__( self, width: float, height: float, angle: float, rotation: float = 0, align: tuple[Align, Align] = (Align.CENTER, Align.CENTER), mode: Mode = Mode.ADD, ): obj = Polygon((width, 0), (width, height), (0, height + width * tan(radians(angle))), (0, width * tan(radians(angle))), align=Align.MIN) super().__init__(obj=obj, rotation=rotation, align=align, mode=mode) class Chevron(BaseSketchObject): def __init__( self, width: float, height: float, angle: float = 22.5, rotation: float = 0, align: tuple[Align, Align] = (Align.CENTER, Align.CENTER), mode: Mode = Mode.ADD, ): chevron = Parallelogram(width/2, height, angle, align=(Align.MIN, Align.MIN)) chevron += mirror(chevron, Plane.YZ) super().__init__(obj=chevron, rotation=rotation, align=align, mode=mode) class NumericalCounter(BasePartObject): def __init__( self, left: str, right: str, width: float = 25 * MM, height: float = 10 * MM, wall: float = 1 * MM, base_z: float = 1 * MM, text_z: float = 0.5 * MM, angle: float = 22.5, rotation: VectorLike = (0, 0, 0), align: tuple[Align, Align] = (Align.CENTER, Align.CENTER), mode: Mode = Mode.ADD, ): base = Chevron(width, height, angle, align=(Align.CENTER, Align.MIN)) part = extrude(base, base_z + text_z, (0, 0, 1)) cutout = Pos(wall/2, wall + wall*tan(radians(angle)), 0) * Parallelogram(width/2 - 1.5*wall, height - 2*wall, angle, align=(Align.MIN, Align.MIN)) cutout += mirror(cutout, Plane.YZ) cutout = Pos(0, 0, base_z + text_z) * cutout part -= extrude(cutout, text_z, (0, 0, -1)) font_size = width/5 font = "Arial Rounded MT Bold" top = Pos(-width/4, height/2 + (wall/2+width/4)*tan(radians(angle))) * Rot(0, 0, angle) * Text(CounterVal(left), font_size, font=font) top += Pos(width/4, height/2 + (wall/2+width/4)*tan(radians(angle))) * Rot(0, 0, -angle) * Text(CounterVal(right), font_size, font=font) part += extrude(Pos(0, 0, base_z) * top, text_z, (0, 0, 1)) super().__init__(part=part, rotation=rotation, align=align, mode=mode) class SVGCounter(BasePartObject): def __init__( self, text: str, image: str, image_yoff: float = 0, font_mul: float = 1, font: str = "Arial Rounded MT Bold", width: float = 25 * MM, height: float = 25 * MM, angle: float = 22.5, wall: float = 1 * MM, base_z: float = 1 * MM, text_z: float = 0.5 * MM, rotation: VectorLike = (0, 0, 0), align: tuple[Align, Align] = (Align.CENTER, Align.CENTER), mode: Mode = Mode.ADD, ): part = extrude(Chevron(width, height, angle, align=(Align.CENTER, Align.MIN)), base_z + text_z, (0, 0, 1)) part -= extrude(Pos(0, wall + wall*tan(radians(angle)), base_z + text_z) * Chevron(width-2*wall, height-2*wall, angle, align=(Align.CENTER, Align.MIN)), text_z, (0, 0, -1)) font_size = width/6 * font_mul top = Pos(0, width/2*tan(radians(angle)) + wall + 2*font_size/3) * Text(text, font_size, font=font, align=(Align.CENTER, Align.NONE)) svg = import_svg(image, align=(Align.CENTER, Align.MIN)) svg_max_x = max([x.bounding_box().max.X for x in svg.faces()]) svg_max_y = max([x.bounding_box().max.Y for x in svg.faces()]) svg_min_x = min([x.bounding_box().min.X for x in svg.faces()]) svg_min_y = min([x.bounding_box().min.Y for x in svg.faces()]) svg_size = (svg_max_x - svg_min_x, svg_max_y - svg_min_y) svg_scale = min(width/svg_size[0] * 0.6, height/svg_size[1] * 0.6) svg_obj = [Pos(0, height + wall + image_yoff*svg_scale - svg_size[1]*svg_scale) * face for face in svg.faces()] top += scale(svg_obj, (svg_scale, svg_scale, svg_scale)) part += extrude(Pos(0, 0, base_z) * top, text_z, (0, 0, 1)) super().__init__(part=part, rotation=rotation, align=align, mode=mode) class Holder(BasePartObject): def __init__( self, rotation: VectorLike = (0, 0, 0), align: tuple[Align, Align] = (Align.CENTER, Align.CENTER), mode: Mode = Mode.ADD, ): cut = 5 * MM max_depth = 36 * MM - cut depth = max_depth part = import_step("multibin_insert_2_2_1.step") insert_top = part.faces().sort_by(SortBy.AREA)[-1] part -= extrude(Plane(insert_top) * (Pos(0, 0, 2*MM) * Rectangle(500*MM, 500*MM)), cut, (0, 0, -1)) part -= extrude(insert_top, cut + depth, (0, 0, -1)) super().__init__(part=part, rotation=rotation, align=align, mode=mode) number_counters = [ (1, 1), (5, 5), (10, 10), (-1, -1), (-5, -5), (1, 0), (5, 0), (10, 0), (0, 1), (0, 5), (0, 10), (1, -1), ] image_counters = { "Deathtouch": (2, 0.9), "Double Strike": (0, 0.8), "First Strike": (0, 1), "Goad": (0, 1), "Flying": (2, 1), "Haste": (-3, 1), "Hexproof": (0, 1), "Indestructible": (0, 0.8), "Lifelink": (-2, 1), "Lore": (0, 1), "Menace": (-2, 1), "Reach": (-3, 1), "Ringbearer": (2, 1), "Shield": (-2, 1), "Stun": (-2, 1), "Time": (0, 1), "Trample": (0, 1), "Vigilance": (-1.5, 1), "Charge": (0, 1), } xoff = 25 * MM yoff = 25 * MM export = True one = True display_objects = [] for idx, numbers in enumerate(number_counters): counter = NumericalCounter(numbers[0], numbers[1]) display_objects.append(Pos((xoff + 1 * MM) * idx, 0) * counter) if export: export_step(counter, f"step/counter_{NumStr(numbers[0])}_{NumStr(numbers[1])}.step") export_stl(counter, f"stl/counter_{NumStr(numbers[0])}_{NumStr(numbers[1])}.stl") if one: break for idx, (text, (y_off, font_mul)) in enumerate(image_counters.items()): counter = SVGCounter(text, f"svg/{text.lower().replace(' ', '')}.svg", y_off, font_mul) display_objects.append(Pos((xoff + 1 * MM) * idx, yoff + 1 * MM) * counter) if export: export_step(counter, f"step/counter_{text.lower().replace(' ', '')}.step") export_stl(counter, f"stl/counter_{text.lower().replace(' ', '')}.stl") if one: break holder = Holder() display_objects.append(Pos(0, 4*xoff) * holder) if export: export_stl(holder, "holder.stl") export_step(holder, "holder.step") show(display_objects)