Turn IModelDoc2.SaveBMP output into PNG/JPEG and read image sizes.
SaveBMP(path, width, height) is the only SolidWorks call that renders the
view at a caller-chosen pixel size; SaveAs3 to .png always writes the
window's size. It only writes BMP, so a PNG is produced here with the
standard library (zlib) - Pillow is an optional extra and absent from the
server's environment. JPEG needs Pillow and reports ImportError without
it, so the caller can fall back.
SaveBMP keeps the window's framing, so an image whose aspect differs
from the window's crops the zoomed-to-fit model. The caller therefore
renders at the window's aspect and :func:pad extends the picture to the
requested size by repeating its edge pixels (the background).
Functions:
convert_bmp
convert_bmp(source: str | Path, target: str | Path, size: tuple[int, int] | None = None) -> None
Convert a BMP to the format named by target's extension.
Parameters:
| Name |
Type |
Description |
Default |
source
|
str | Path
|
|
required
|
target
|
str | Path
|
Output path; .png, .bmp, .jpg or .jpeg.
|
required
|
size
|
tuple[int, int] | None
|
Pad the picture to this (width, height) (see :func:pad).
|
None
|
Raises:
| Type |
Description |
ImportError
|
For JPEG when Pillow is not installed.
|
ValueError
|
For any other extension, a BMP that :func:read_bmp
cannot read, or one larger than size.
|
Source code in src/solidworks_mcp/adapters/solidworks/bitmap.py
| def convert_bmp(
source: str | Path, target: str | Path, size: tuple[int, int] | None = None
) -> None:
"""Convert a BMP to the format named by ``target``'s extension.
Args:
source: The BMP ``SaveBMP`` wrote.
target: Output path; ``.png``, ``.bmp``, ``.jpg`` or ``.jpeg``.
size: Pad the picture to this ``(width, height)`` (see :func:`pad`).
Raises:
ImportError: For JPEG when Pillow is not installed.
ValueError: For any other extension, a BMP that :func:`read_bmp`
cannot read, or one larger than ``size``.
"""
suffix = Path(target).suffix.lower()
if suffix not in (".png", ".bmp", ".jpg", ".jpeg"):
raise ValueError(f"Cannot convert a BMP to {suffix!r}")
width, height, rows = read_bmp(source)
if size is not None:
rows = pad(width, height, rows, size)
width, height = size
if suffix == ".png":
write_png(target, width, height, rows)
elif suffix == ".bmp":
write_bmp(target, width, height, rows)
else:
from PIL import Image
image = Image.frombytes("RGB", (width, height), b"".join(rows))
image.save(target, "JPEG", quality=92)
|
fit_aspect
fit_aspect(width: int, height: int, aspect: float) -> tuple[int, int]
Largest aspect (width / height) size that fits in width x height.
Source code in src/solidworks_mcp/adapters/solidworks/bitmap.py
| def fit_aspect(width: int, height: int, aspect: float) -> tuple[int, int]:
"""Largest ``aspect`` (width / height) size that fits in ``width`` x ``height``."""
if width / height > aspect:
return max(1, round(height * aspect)), height
return width, max(1, round(width / aspect))
|
image_size
image_size(path: str | Path) -> tuple[int, int] | None
Return (width, height) of a PNG, BMP or JPEG, or None.
Source code in src/solidworks_mcp/adapters/solidworks/bitmap.py
| def image_size(path: str | Path) -> tuple[int, int] | None:
"""Return ``(width, height)`` of a PNG, BMP or JPEG, or ``None``."""
try:
data = Path(path).read_bytes()
except OSError:
return None
if data[:8] == b"\x89PNG\r\n\x1a\n" and len(data) >= 24:
return struct.unpack_from(">II", data, 16)
if data[:2] == b"BM" and len(data) >= 26:
width, height = struct.unpack_from("<ii", data, 18)
return width, abs(height)
if data[:2] == b"\xff\xd8":
index = 2
while index + 9 <= len(data) and data[index] == 0xFF:
marker = data[index + 1]
length = struct.unpack_from(">H", data, index + 2)[0]
# SOF0..SOF15 carry the frame size; C4/C8/CC are not frames.
if 0xC0 <= marker <= 0xCF and marker not in (0xC4, 0xC8, 0xCC):
height, width = struct.unpack_from(">HH", data, index + 5)
return width, height
index += 2 + length
return None
|
pad
pad(width: int, height: int, rows: list[bytes], target: tuple[int, int]) -> list[bytes]
Centre RGB rows in a target canvas, repeating the edge pixels.
Raises:
| Type |
Description |
ValueError
|
When the image is larger than target.
|
Source code in src/solidworks_mcp/adapters/solidworks/bitmap.py
| def pad(
width: int, height: int, rows: list[bytes], target: tuple[int, int]
) -> list[bytes]:
"""Centre RGB ``rows`` in a ``target`` canvas, repeating the edge pixels.
Raises:
ValueError: When the image is larger than ``target``.
"""
target_w, target_h = target
if width > target_w or height > target_h:
raise ValueError(f"{width}x{height} does not fit in {target_w}x{target_h}")
left = (target_w - width) // 2
right = target_w - width - left
widened = [row[:3] * left + row + row[-3:] * right for row in rows]
top = (target_h - height) // 2
bottom = target_h - height - top
return [widened[0]] * top + widened + [widened[-1]] * bottom
|
read_bmp
read_bmp(path: str | Path) -> tuple[int, int, list[bytes]]
Read an uncompressed 24/32-bit BMP.
Returns:
| Type |
Description |
int
|
tuple[int, int, list[bytes]]: Width, height and the RGB rows, top
|
int
|
|
Raises:
| Type |
Description |
ValueError
|
When the file is not an uncompressed 24/32-bit BMP.
|
Source code in src/solidworks_mcp/adapters/solidworks/bitmap.py
| def read_bmp(path: str | Path) -> tuple[int, int, list[bytes]]:
"""Read an uncompressed 24/32-bit BMP.
Returns:
tuple[int, int, list[bytes]]: Width, height and the RGB rows, top
row first.
Raises:
ValueError: When the file is not an uncompressed 24/32-bit BMP.
"""
data = Path(path).read_bytes()
if len(data) < 54 or data[:2] != b"BM":
raise ValueError(f"{path} is not a BMP file")
offset = struct.unpack_from("<I", data, 10)[0]
width, height = struct.unpack_from("<ii", data, 18)
bits, compression = struct.unpack_from("<HI", data, 28)
# BI_RGB (0), or BI_BITFIELDS (3) with the standard BGRA masks at 32 bpp.
if bits not in (24, 32) or compression not in (0, 3) or width <= 0:
raise ValueError(
f"{path}: unsupported BMP ({bits} bpp, compression {compression})"
)
step = bits // 8
stride = (width * step + 3) & ~3
top_down = height < 0
height = abs(height)
rows = []
for index in range(height):
start = offset + index * stride
bgr = data[start : start + width * step]
rgb = bytearray(width * 3)
rgb[0::3] = bgr[2::step]
rgb[1::3] = bgr[1::step]
rgb[2::3] = bgr[0::step]
rows.append(bytes(rgb))
if not top_down:
rows.reverse()
return width, height, rows
|
write_bmp
write_bmp(path: str | Path, width: int, height: int, rows: list[bytes]) -> None
Write RGB rows (top first) as a bottom-up 24-bit BMP.
Source code in src/solidworks_mcp/adapters/solidworks/bitmap.py
| def write_bmp(path: str | Path, width: int, height: int, rows: list[bytes]) -> None:
"""Write RGB rows (top first) as a bottom-up 24-bit BMP."""
stride = (width * 3 + 3) & ~3
body = bytearray()
for row in reversed(rows):
bgr = bytearray(width * 3)
bgr[0::3] = row[2::3]
bgr[1::3] = row[1::3]
bgr[2::3] = row[0::3]
body += bgr + b"\x00" * (stride - width * 3)
header = struct.pack(
"<2sIHHIIiiHHIIiiII",
*(b"BM", 54 + len(body), 0, 0, 54, 40, width, height, 1, 24, 0),
*(len(body), 2835, 2835, 0, 0),
)
Path(path).write_bytes(header + bytes(body))
|
write_png
write_png(path: str | Path, width: int, height: int, rows: list[bytes]) -> None
Write 8-bit RGB rows (top first) as a PNG.
Source code in src/solidworks_mcp/adapters/solidworks/bitmap.py
| def write_png(path: str | Path, width: int, height: int, rows: list[bytes]) -> None:
"""Write 8-bit RGB rows (top first) as a PNG."""
def chunk(kind: bytes, body: bytes) -> bytes:
return (
struct.pack(">I", len(body))
+ kind
+ body
+ struct.pack(">I", zlib.crc32(kind + body) & 0xFFFFFFFF)
)
raw = b"".join(b"\x00" + row for row in rows)
Path(path).write_bytes(
b"\x89PNG\r\n\x1a\n"
+ chunk(b"IHDR", struct.pack(">IIBBBBB", width, height, 8, 2, 0, 0, 0))
+ chunk(b"IDAT", zlib.compress(raw, 6))
+ chunk(b"IEND", b"")
)
|