ODB++ 예제와 읽는 코드
ODB++ 는 텍스트 파일로 이루어진 디렉터리 트리다. 열어서 읽을 수 있고, 읽으면 구조가 그대로 드러난다.
1. 디렉터리 트리
섹션 제목: “1. 디렉터리 트리”myboard/├── matrix/│ └── matrix 층의 이름 · 종류 · 순서├── steps/│ └── board/│ ├── stephdr 스텝의 머리 — 원점과 크기│ ├── profile 보드 외곽│ ├── layers/│ │ ├── top/features 앞면 구리의 형상│ │ ├── bot/features 뒷면 구리의 형상│ │ └── drill/features 홀│ ├── eda/│ │ └── data 넷 연결│ └── components_top 앞면 부품├── symbols/ 사용자 심볼├── fonts/ 문자 정의└── misc/ └── info 작업 정보층 하나가 디렉터리 하나이고 그 안의 형상 파일 이름은 늘 같다. 파일 이름이 뜻을 정하지 않으므로 받는 쪽이 이름 규칙을 알 필요가 없다.
2. 피처 파일
섹션 제목: “2. 피처 파일”##Layer features#$0 c0.2$1 r0.8x1.2#L 10.0 5.0 30.0 5.0 0 P 0L 30.0 5.0 30.0 25.0 0 P 0P 12.0 8.0 1 P 0 0S P 0OB 40.0 10.0 IOS 60.0 10.0OS 60.0 30.0OS 40.0 30.0OESE| 줄 | 뜻 |
|---|---|
$0 c0.2 | 0 번 심볼(symbol)은 지름 0.2 의 원이다 |
$1 r0.8x1.2 | 1 번 심볼은 0.8 × 1.2 의 사각이다 |
L 10.0 5.0 30.0 5.0 0 P 0 | (10,5) 에서 (30,5) 까지 이어지는 0 번 심볼의 선 피처(feature) · 극성(polarity) P |
L 30.0 5.0 30.0 25.0 0 P 0 | 이어지는 구간 — 트랙(track)이 꺾인 자리 |
P 12.0 8.0 1 P 0 0 | (12,8) 에 1 번 심볼로 배치한 패드(pad) |
S P 0 | 채움면 시작 |
OB 40.0 10.0 I | 윤곽 시작점 |
OS … | 윤곽의 다음 점 |
OE SE | 윤곽 끝 · 채움면 끝 |
선 줄에 굵기가 없다. 굵기는 $0 이 정하고 줄은 번호만 가리킨다. 같은 굵기의 트랙 수천 개가
번호 하나를 공유하므로 파일이 작아지고, 굵기를 바꾸면 한 곳만 고치면 된다.
3. 넷 자료
섹션 제목: “3. 넷 자료”H optimize Y#NET GNDNET VBUS#0 0.35 12.0 8.0 T e c1 0.35 30.0 25.0 T m c v| 줄 | 뜻 |
|---|---|
H optimize Y | 머리 — 자료가 정리된 상태인가 |
NET GND | 넷 이름 목록. 뒤의 줄이 번호로 이것을 가리킨다 |
0 0.35 12.0 8.0 T e c | 0 번 넷의 단자 — 반지름 0.35 · 좌표 · 앞면 · 끝점 · 노출됨 |
1 0.35 30.0 25.0 T m c v | 1 번 넷의 단자 — 중간점 · 비아다 |
넷마다 어느 좌표에 어떤 단자가 있는지 적히므로, 받는 쪽은 형상에서 연결을 추출하지 않는다.
4. 레코드를 갈라 읽는 최소 코드
섹션 제목: “4. 레코드를 갈라 읽는 최소 코드”피처 파일은 첫 글자로 갈래가 정해지는 줄의 나열이다. 읽는 쪽의 첫 단계는 주석과 심볼 정의를 걸러 내고 갈래를 나누는 일이다.
#include <string>#include <string_view>#include <vector>
namespace codename7{ /// ODB++ 피처 파일의 한 줄. struct s_odb_record { char kind = 0; ///< L · P · A · S · T · B, 심볼 정의는 '$' std::vector<std::string> fields; ///< 공백으로 가른 나머지 };
class c_odb_feature_reader { public: /// 주석과 빈 줄을 걸러 내고 레코드로 만든다. static std::vector<s_odb_record> Parse(std::string_view aText) { std::vector<s_odb_record> out; size_t pos = 0; while (pos <= aText.size()) { const size_t nl = aText.find('\n', pos); std::string_view line = aText.substr(pos, (nl == std::string_view::npos ? aText.size() : nl) - pos); pos = (nl == std::string_view::npos) ? aText.size() + 1 : nl + 1;
line = trim(line); if (line.empty() || line.front() == '#') continue; // 주석과 빈 줄
s_odb_record rec; rec.kind = line.front(); rec.fields = split(line.substr(1)); out.push_back(std::move(rec)); } return out; }
private: static std::string_view trim(std::string_view s) { while (!s.empty() && isBlank(s.front())) s.remove_prefix(1); while (!s.empty() && isBlank(s.back())) s.remove_suffix(1); return s; }
static bool isBlank(char c) { return c == ' ' || c == '\t' || c == '\r'; }
static std::vector<std::string> split(std::string_view s) { std::vector<std::string> out; size_t i = 0; while (i < s.size()) { while (i < s.size() && isBlank(s[i])) ++i; const size_t start = i; while (i < s.size() && !isBlank(s[i])) ++i; if (i > start) out.emplace_back(s.substr(start, i - start)); } return out; } };}갈래를 나눈 뒤에는 종류마다 필드를 해석한다.
| 종류 | 필드 순서 |
|---|---|
L | 시작 x · 시작 y · 끝 x · 끝 y · 심볼 번호 · 극성 · 코드 |
P | x · y · 모양 참조 · 극성 · 코드 · 방향 |
S | 극성 · 코드 (뒤이어 윤곽, SE 로 닫는다) |
$ | 심볼 번호와 정의 |
5. 낸 자료를 확인하는 순서
섹션 제목: “5. 낸 자료를 확인하는 순서”| 순서 | 하는 일 |
|---|---|
| 1 | 층 목록을 열어 순서와 종류를 확인한다 |
| 2 | 각 층의 피처 파일이 비어 있지 않은지 본다 |
| 3 | 넷 자료의 넷 수를 설계와 대조한다 |
| 4 | 부품 수와 면 배분을 대조한다 |
| 5 | 압축한 묶음을 다시 풀어 트리가 온전한지 본다 |
3 번이 이 형식에서 새로 생기는 검증이다. 형상과 넷이 함께 담기므로, 둘이 서로 맞는지가 확인 대상이 된다.