Update Pier_v1 project

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
CopilotandCopilot committed 2026-11-03 12:04:58 +09:00
1 parent f25e36c050
commit ad73ddec2b
2476 files changed
+3423 -4902

No files matched your search

@@ -0,0 +1,9 @@
"""Public, GUI-free bridge drawing extraction API."""
from .api import ExtractionResult, WorkbookResult, extract_folder, check_environment
from .core import ExtractionError
__version__ = '0.1.9'
__all__ = ['extract_folder', 'check_environment', 'ExtractionResult', 'WorkbookResult', 'ExtractionError']
@@ -0,0 +1,2 @@
from .cli import main
raise SystemExit(main())
@@ -0,0 +1,86 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable
import platform
from importlib.metadata import version
from .automatic import run_folder
from .core import ExtractionError, converter_path as find_converter
@dataclass(frozen=True)
class WorkbookResult:
name: str
xlsx_path: Path
json_path: Path
review_csv_path: Path
data: dict[str, Any]
@property
def review_count(self) -> int:
return sum(r['status'] not in ['추출','식별자','등록값'] for r in self.data['records'])
def to_dict(self, *, include_data: bool = True) -> dict[str, Any]:
result = dict(name=self.name, xlsx_path=str(self.xlsx_path), json_path=str(self.json_path),
review_csv_path=str(self.review_csv_path), review_count=self.review_count)
if include_data:
result['data'] = self.data
return result
@dataclass(frozen=True)
class ExtractionResult:
output_dir: Path
workbooks: tuple[WorkbookResult, ...]
files: tuple[dict[str, Any], ...]
schema_version: str = '1.0'
@property
def review_count(self) -> int:
return sum(book.review_count for book in self.workbooks)
@property
def failed_file_count(self) -> int:
return sum(f['status'] == '읽기 실패' for f in self.files)
@property
def status(self) -> str:
return 'needs_review' if self.review_count or self.failed_file_count else 'completed'
def to_dict(self, *, include_data: bool = True) -> dict[str, Any]:
return dict(schema_version=self.schema_version, status=self.status,
output_dir=str(self.output_dir), review_count=self.review_count,
failed_file_count=self.failed_file_count,
workbooks=[b.to_dict(include_data=include_data) for b in self.workbooks], files=list(self.files))
def extract_folder(
folder: str | Path,
*,
output_dir: str | Path | None = None,
converter_path: str | Path | None = None,
on_progress: Callable[[str], None] | None = None,
) -> ExtractionResult:
"""Read a DWG folder synchronously and return paths, cell data and review statuses.
No GUI or network calls. Provide LibreDWG dwgread externally. On failure,
raises ExtractionError. Partial file failures remain visible in result.files.
Use a worker thread/process when calling from a GUI or async server.
"""
try:
directory, outputs, files = run_folder(folder, output_dir, converter_path, on_progress or (lambda _: None))
except ExtractionError:
raise
except (OSError, ValueError) as exc:
raise ExtractionError(str(exc)) from exc
books=tuple(WorkbookResult(data['name'],path.resolve(),path.with_suffix('.json').resolve(),
path.with_suffix('.csv').resolve(),data) for path,data in outputs)
return ExtractionResult(directory.resolve(),books,tuple(files))
def check_environment(*, converter_path: str | Path | None = None) -> dict[str, Any]:
"""Check dependencies without converting any files or starting a GUI."""
result = dict(python=platform.python_version(), openpyxl=version('openpyxl'), converter_available=False)
try:
result.update(converter_available=True, converter_path=str(find_converter(converter_path)))
except ExtractionError as exc:
result['converter_error']=str(exc)
return result
@@ -0,0 +1,314 @@
"""Folder-only workflow: inspect CAD contents, infer table structure, relocate landmarks."""
import copy
import csv
from datetime import datetime
from pathlib import Path
import re
import math
from itertools import combinations
from .core import APP,HEADERS,ROLES,ExtractionError,convert,converter_path,read_json,write_json,extract
from .semantic import Index,norm,classify,bearings,pier,pier_tables,calculation_elevations,drawing_direction,pile_minimum_m
from .workbook import export_workbook
from .inference import coordinate_points,associate_ips,profile_fields,integrated_bearings
from .geometry import LineIndex
EXCLUDE={'backup','temp','outputs','code','rag','node_modules','__pycache__','_도면추출결과'}
PRIMARY_ROLES={'01','02','03'}
SOURCE_POLICY={
'Pier':{'C':'종평면 형식 표기, 미확인 시 T형','D':'03:교좌면EL 최솟값','E':'01','F':'01','G':'01','H':'01:말뚝길이 최솟값 mm','I':'02:전후열 중심거리+2000mm, 500mm 올림','J':'02:IP1-IP3 거리 초과 500mm','K':'T형 1개 / π형 2개'},
'IP':{'D':'02','E':'02'},
'Bearing':{'D':'02','E':'02','F':'03:교량받침EL(몰탈상단)','G':'02','H':'02:받침 표제의 전열/후열'},
}
def discover(folder):
root=Path(folder).resolve()
if not root.is_dir():raise ExtractionError('도면 폴더가 없습니다.')
files=[]
for p in root.rglob('*'):
rel=p.relative_to(root)
if any(part.casefold() in EXCLUDE or part.startswith('.') for part in rel.parts[:-1]):continue
if p.is_file() and p.suffix.lower()=='.dwg':files.append(p)
if not files:raise ExtractionError('폴더와 하위 폴더에 DWG 파일이 없습니다.')
return sorted(files)
def choose_layout(documents):
scored=[]
for path in (APP/'layouts').glob('*.json'):
p=read_json(path);scores=[]
for d,s in p['sources'].items():
if d not in documents:continue
if documents[d]['sha256']==s['sha256']:scores.append(1);continue
sig=set(s.get('signature',[]));ts={norm(e['t']) for e in documents[d]['texts']}
scores.append(len(sig&ts)/len(sig) if sig else 0)
score=sum(scores)/3
if score>=.55:scored.append((score,p))
scored.sort(key=lambda x:-x[0])
if not scored or len(scored)>1 and abs(scored[0][0]-scored[1][0])<.05:return None
return scored[0][1]
def adaptive_result(profile,documents):
"""Move numerical locators via text landmarks; never move manual geometry defaults."""
p=copy.deepcopy(profile);indices={d:Index(v['texts']) for d,v in documents.items()}
# The general drawing is intentionally unavailable under the current policy.
p['rules']=[r for r in p['rules'] if not (r['sheet']=='Pier' and r['cell'][0] in 'CDIJK') and not (r['sheet']=='Bearing' and r['cell'][0]=='F')]
p['notes']=[]
for rule in p['rules']:
if rule['sheet']=='Pier' and rule['cell'].startswith('H'):rule['pile_minimum']=True
for ref in rule.get('refs',[])+rule.get('guards',[]):
d=ref['doc']
if d not in documents:
rule['op']='missing';rule['note']='해당 역할의 도면을 찾지 못했습니다.';break
if documents[d]['sha256']==p['sources'][d]['sha256']:continue
entity=indices[d].locate(ref)
if entity:
ref.update(handle=entity['h'],x=entity['x'],y=entity['y'])
else:
# A failed semantic relocation must not accidentally fall back to
# an unrelated numeric value remaining at the former coordinate.
rule['op']='missing';rule['note']='제목·주변 문자로 추출 위치를 유일하게 대응하지 못했습니다.';break
return extract(p,documents)
def blank_result(name):
return dict(name=name,profile='automatic',cells={s:{} for s in HEADERS},records=[],changed_sources=[],notes=[])
def setcell(result,sheet,cell,value,status='추출',messages=None,evidence=None):
result['cells'][sheet][cell]=value
result['records']=[r for r in result['records'] if not(r['sheet']==sheet and r['cell']==cell)]
result['records'].append(dict(sheet=sheet,cell=cell,value=value,status=status,messages=messages or [],note='도면 내용 및 상대 위치 자동 판독',evidence=evidence or []))
def combine_values(entries,field):
values={e[field] for e in entries}
if len(values)==1:return entries[0][field],'추출',[]
return None,'미확정',['복수 도면/표의 값 상충: '+' / '.join(map(str,sorted(values,key=str)))]
def next_500(distance_m):
# Remove sub-micrometre coordinate arithmetic noise at exact half-metres.
return (math.floor(round(distance_m*1000,6)/500)+1)*500
def column_count(pier_type):
return {'T형':1,'T':1,'Π형':2,'Π':2,'PI형':2}.get(norm(str(pier_type)))
def bearing_row_width(cells,rows):
groups={'전열':[],'후열':[]}
for row in rows:
kind=cells.get(f'H{row}');xy=[cells.get(f'{c}{row}') for c in 'DE']
if kind not in groups or not all(isinstance(v,(int,float)) and math.isfinite(v) for v in xy):return None,None
groups[kind].append(xy)
if not all(groups.values()):return None,None
centers=[tuple(sum(p[k] for p in points)/len(points) for k in [0,1]) for points in groups.values()]
distance_mm=math.dist(*centers)*1000
if distance_mm<=1e-6:return None,None
return math.ceil(round(distance_mm+2000,6)/500)*500,distance_mm
def apply_input_conventions(result):
for cell,p in list(result['cells']['Pier'].items()):
if not cell.startswith('B'):continue
row=cell[1:];kind=result['cells']['Pier'].get('C'+row)
count=column_count(kind)
setcell(result,'Pier','K'+row,count,'기본값' if count else '미확인',[f'형식별 지정값: T형 1개, π형 2개. 현재 형식 {kind}'])
record=next((r for r in result['records'] if r['sheet']=='Pier' and r['cell']=='H'+row),None)
value=result['cells']['Pier'].get('H'+row);length=pile_minimum_m(value)
setcell(result,'Pier','H'+row,round(length*1000,6) if length is not None else None,
'산정값' if length is not None else '미확인',
[f'말뚝길이 최솟값 선택 후 m→mm 변환. 변환 전 값: {value}']+((record or {}).get('messages',[])),
(record or {}).get('evidence',[]))
def apply_derived_piers(result,zrows):
cells=result['cells']
def rows_for(sheet,p):
return sorted(int(c[1:]) for c,v in cells[sheet].items() if c.startswith('B') and v==p)
def point(sheet,row):
xy=[cells[sheet].get(f'{c}{row}') for c in 'DE']
return tuple(xy) if all(isinstance(v,(int,float)) and math.isfinite(v) for v in xy) else None
def evidence(sheet,rows):
return [e for r in result['records'] if r['sheet']==sheet and r['cell'][0] in 'DE' and int(r['cell'][1:]) in rows for e in r['evidence']]
for cell,p in list(cells['Pier'].items()):
if not cell.startswith('B'):continue
row=int(cell[1:])
if not cells['Pier'].get(f'C{row}'):setcell(result,'Pier',f'C{row}','T형','기본값',['사용자 지정 기본 교각형식. 형상 판독값 아님.'])
entries=[e for e in zrows if e['p']==p and 'seat' in e]
grouped={no:[e for e in entries if e['no']==no] for no in {e['no'] for e in entries}}
seats=[combine_values(es,'seat')[0] for es in grouped.values()]
expected={cells['Bearing'].get(f'C{r}') for r in rows_for('Bearing',p)}
if seats and None not in seats and expected.issubset(grouped):
setcell(result,'Pier',f'D{row}',min(seats),'산정값',['교각별 교좌면 EL 최솟값 (m)'],[e for r in entries for e in r['evidence']])
else:setcell(result,'Pier',f'D{row}',None,'미확정',['교좌면 EL 누락 또는 동일 받침의 복수 값 상충'],[e for r in entries for e in r['evidence']])
br=rows_for('Bearing',p);width,dist=bearing_row_width(cells['Bearing'],br)
if width is not None:
setcell(result,'Pier',f'I{row}',width,'산정값',[f'전열/후열 받침 좌표 평균점 사이 거리 {dist:.6f} mm + 2000mm, 500mm 단위 올림'],evidence('Bearing',br)+[e for r in result['records'] if r['sheet']=='Bearing' and r['cell'].startswith('H') and int(r['cell'][1:]) in br for e in r['evidence']])
else:setcell(result,'Pier',f'I{row}',None,'미확인',['폭 산정에 필요한 전열·후열 구분 또는 좌표 부족/상충'])
ip=rows_for('IP',p);selected=[]
for number in [1,3]:
matches=[r for r in ip if norm(str(cells['IP'].get(f'C{r}'))).replace('.','') in [str(number),f'IP{number}']]
if len(matches)==1:selected.append(matches[0])
points=[point('IP',r) for r in selected]
if len(points)==2 and all(v is not None for v in points) and math.dist(*points)>0:
dist=math.dist(*points)
setcell(result,'Pier',f'J{row}',next_500(dist),'산정값',[f'IP1-IP3 평면거리 {dist*1000:.6f} mm를 초과하는 다음 500mm'],evidence('IP',selected))
else:setcell(result,'Pier',f'J{row}',None,'미확인',['길이 산정에 필요한 유일한 IP1·IP3 좌표 부족/상충'])
def assemble_group(name,documents_by_role):
# Multiple revisions are all read. Do not silently choose a "latest" filename.
documents_by_role={d:v for d,v in documents_by_role.items() if d!='04'}
simple={d:items[0][1] for d,items in documents_by_role.items() if len(items)==1}
layout=choose_layout(simple)
if layout:result=adaptive_result(layout,{d:simple[d] for d in ['01','02'] if d in simple})
else:result=blank_result(name)
result['name']=name;result['profile']='automatic'
sources={};brows=[];zrows=[];prows=[];observed=set();ip_points=[]
result['source_policy']=SOURCE_POLICY
result['supplementary_drawings']=[]
directions={drawing_direction(e['t']) for _,data in documents_by_role.get('02',[]) for e in data['texts']}-{None}
for doc,items in documents_by_role.items():
for index,(path,data) in enumerate(items):
lines=LineIndex(data.get('segments')) if data.get('segments') else None
key=doc if len(items)==1 else f'{doc}_{index+1}'
sources[key]=dict(path=str(path),sha256=data['sha256'])
if doc=='03':
result['supplementary_drawings'].append(dict(doc=key,role=ROLES[doc],
status='표고 추출 원문',texts=data['texts'],
note='받침 Z=몰탈상단, 두부보 EL=교좌면 EL 최솟값'))
rows,issues=calculation_elevations(data['texts'],lines);result['notes'].extend(issues)
for b in integrated_bearings(data['texts'],rows):
b['evidence']=[dict(doc=key,**e) for e in b['evidence']];brows.append(b);observed.add(b['p'])
for row in rows:
if len(directions)==1 and row['direction'] is not None and row['direction'] not in directions:continue
row['evidence']=[dict(doc=key,**e) for e in row['evidence']];zrows.append(row);observed.add(row['p'])
if doc=='01':
for row in pier_tables(data['texts'],doc)+profile_fields(data['texts'],lines):
row['evidence']=[dict(doc=key,**e) for e in row['evidence']];prows.append(row);observed.add(row['p'])
if doc=='02':
for point in coordinate_points(data['texts']):
point['evidence']=[dict(doc=key,**e) for e in point['evidence']];ip_points.append(point)
rows,issues=bearings(data['texts'],lines);result['notes'].extend(issues)
for row in rows:
row['evidence']=[dict(doc=key,**e) for e in row['evidence']];brows.append(row);observed.add(row['p'])
primary_piers={r['p'] for r in brows+zrows}
if primary_piers:
observed=primary_piers
prows=[r for r in prows if r['p'] in primary_piers]
result['sources']=sources
if len(directions)>1:result['notes'].append('복수 방향 도면: 교각/받침 번호가 겹치면 상충값으로 보존하며 임의 방향을 선택하지 않음.')
if brows or zrows:
result['cells']['Bearing']={};result['records']=[r for r in result['records'] if r['sheet']!='Bearing']
keys=sorted({(r['p'],r['no']) for r in brows+zrows},key=lambda x:(int(x[0][1:]),x[1]))
for row,(p,no) in enumerate(keys,3):
setcell(result,'Bearing',f'B{row}',p,'식별자');setcell(result,'Bearing',f'C{row}',no,'식별자')
for col,field,entries in [('D','x',brows),('E','y',brows),('F','z',zrows),('G','angle',brows),('H','row_type',brows)]:
es=[e for e in entries if e['p']==p and e['no']==no and field in e]
if es:
value,status,messages=combine_values(es,field)
setcell(result,'Bearing',f'{col}{row}',value,status,messages,[e for r in es for e in r['evidence']])
else:setcell(result,'Bearing',f'{col}{row}',None,'미확인',[f'{p} 받침 {no}: 대응 표를 찾지 못했습니다.'])
# Newly discovered piers are never silently omitted because an old layout had fewer rows.
existing={v for c,v in result['cells']['Pier'].items() if c.startswith('B')}
for p in sorted(observed-existing,key=lambda p:int(p[1:])):
row=max([int(c[1:]) for c in result['cells']['Pier']]+[2])+1
setcell(result,'Pier',f'B{row}',p,'식별자')
for col in 'CDEFGHIJK':setcell(result,'Pier',f'{col}{row}',None,'미확인',['형상·치수·종단 값의 교각 대응 근거 미확인'])
for p in observed:
rows=[int(c[1:]) for c,v in result['cells']['Pier'].items() if c.startswith('B') and v==p]
if len(rows)!=1:continue
for col in 'CDEFGHIJK':
entries=[e for e in prows if e['p']==p and e['col']==col]
if entries:
explicit=[e for e in entries if not e.get('inferred')]
if explicit:entries=explicit
elif layout:continue # Preserve audited locators, including unresolved conflicts.
value,status,messages=combine_values(entries,'value')
if col=='H':
lengths=[pile_minimum_m(e['value']) for e in entries]
value=min(lengths) if None not in lengths else None
status='산정값' if value is not None else '미확인';messages=['말뚝길이 표기의 최솟값 선택']
if any(e.get('inferred') for e in entries):
messages+=['교각 표기와 종평면 인접 문자의 상대 위치로 대응: 지시선 연결 검토 필요']
if value is not None:status='검토필요'
setcell(result,'Pier',f'{col}{rows[0]}',value,status,messages,[e for r in entries for e in r['evidence']])
# World-coordinate proximity, ambiguity rejection, then CAD top-to-bottom ordering.
linked=associate_ips(ip_points,brows)
if not result['cells']['IP']:
row=3
for p in sorted(observed,key=lambda p:int(p[1:])):
points=linked.get(p,[])
for i in range(4):
point=points[i] if i<3 and i<len(points) else None
setcell(result,'IP',f'B{row}',p,'식별자')
setcell(result,'IP',f'C{row}',f'IP{i+1}','식별자')
for col,field in [('D','x'),('E','y')]:
setcell(result,'IP',f'{col}{row}',point[field] if point else None,'산정값' if point else '미확인',
['받침 좌표 범위와 지시점 거리로 교각 대응, CAD 위→아래 순서' if point else '교각에 유일하게 연결되는 IP 3점 미확인'],point['evidence'] if point else [])
if i==3:
for col in 'DEF':setcell(result,'IP',f'{col}{row}',None,'기본값',['IP4는 빈칸으로 유지'])
else:setcell(result,'IP',f'F{row}',None,'대상외',['꺾임각은 요청 항목 외'])
row+=1
max_ip_row=max([int(a[1:]) for a in result['cells']['IP']]+[2])
for p in observed:
rows=[r for r in range(3,max_ip_row+1) if result['cells']['IP'].get(f'B{r}')==p]
fourth=[r for r in rows if norm(str(result['cells']['IP'].get(f'C{r}'))).replace('.','') in ['4','IP4']]
if fourth:
for r in fourth:
for col in 'DEF':setcell(result,'IP',f'{col}{r}',None,'기본값',['IP4는 빈칸으로 유지'])
continue
row=max_ip_row+1;max_ip_row=row
for col,value in [('B',p),('C','IP4'),('D',None),('E',None),('F',None)]:
setcell(result,'IP',f'{col}{row}',value,'식별자' if col in 'BC' else '기본값',['IP4는 빈칸으로 유지'] if col not in 'BC' else [])
result['coordinate_callouts']=ip_points
apply_derived_piers(result,zrows)
apply_input_conventions(result)
result['notes']+=['종평면 형식 표기 우선, 미확인 시 기본 T형. 두부보 치수는 좌표 기반 산정값.',
'폭=전후열 좌표 평균점 거리+2000mm를 500mm 올림. 길이=IP1-IP3 거리를 초과하는 다음 500mm.',
'받침 Z=ELEVATION 몰탈상단, 두부보 EL=교각별 교좌면 EL 최솟값.',
'폴더 자동 판독: 파일명 대신 도면 문자 내용으로 역할을 분류.',
'좌표표 열은 헤더로 인식. 기타 값은 제목·주변 문자의 상대 배치를 대조.',
'일반 형상 인식/OCR은 지원하지 않음. 근거가 모호한 값은 공란·검토 항목으로 보존.']
for d in PRIMARY_ROLES:
if d not in documents_by_role:result['notes'].append(f'{ROLES[d]} 역할 도면 미발견')
return result
def run_folder(folder,output=None,converter=None,log=lambda s:None):
root=Path(folder).resolve();files=discover(root)
output=Path(output).resolve() if output else root/'_도면추출결과'
output.mkdir(parents=True,exist_ok=True);exe=converter_path(converter)
groups={};inventory=[]
for path in files:
if output==path.parent or output in path.parents:continue
try:
data=convert(path,output/'.cache',exe,log);roles=classify(data['texts'])
inventory.append(dict(file=str(path),roles=roles,status='인식' if roles else '역할 미확정',text_count=len(data['texts'])))
log(f'{path.name}: '+(', '.join(ROLES[d] for d in roles) or '문자 읽기 완료, 역할 미확정'))
# Each folder is an independent drawing set: identical P1 IDs belonging
# to different bridges must never be combined accidentally.
for d in roles:
if d=='04':continue # User policy: general pier drawings are not inputs.
bucket=groups.setdefault(path.parent,{}).setdefault(d,[])
if not any(old['sha256']==data['sha256'] for _,old in bucket):bucket.append((path,data))
except Exception as e:
inventory.append(dict(file=str(path),roles=[],status='읽기 실패',reason=str(e)));log(f'{path.name}: 읽기 실패 — {e}')
if not groups:
write_json(output/'파일판독내역.json',inventory)
raise ExtractionError('모든 DWG를 읽었으나 대상 역할을 식별하지 못했습니다. 파일판독내역.json을 확인하세요.')
# A single unambiguous set may be distributed over category subfolders.
if len(groups)>1 and all(sum(len(g.get(d,[])) for g in groups.values())<=1 for d in ROLES):
merged={}
for g in groups.values():
for d,items in g.items():merged.setdefault(d,[]).extend(items)
groups={root:merged}
destdir=output/datetime.now().strftime('%Y%m%d_%H%M%S_%f');destdir.mkdir()
write_json(destdir/'파일판독내역.json',inventory);outputs=[]
for group,docs in groups.items():
simple={d:a[0][1] for d,a in docs.items() if len(a)==1};layout=choose_layout(simple)
unchanged=layout and all(d in simple and simple[d]['sha256']==s['sha256'] for d,s in layout['sources'].items())
name=layout['name'] if unchanged else group.name
log(f'{name}: 표·교각 번호 및 상대 배치 분석 중')
result=assemble_group(name,docs)
if not any(result['cells'][s] for s in HEADERS):continue
safe=re.sub(r'[^\w가-힣().-]+','_',name)[:45]
target=destdir/(safe+f'_{len(outputs)+1}_교각입력데이터.xlsx')
export_workbook(result,target);write_json(target.with_suffix('.json'),result)
with target.with_suffix('.csv').open('w',encoding='utf-8-sig',newline='') as f:
w=csv.writer(f);w.writerow(['시트','셀','상태','값','사유'])
for r in result['records']:
if r['status'] not in ['식별자','추출','등록값']:w.writerow([r['sheet'],r['cell'],r['status'],r['value'],' / '.join(r['messages'])])
outputs.append((target,result));log(f'엑셀 저장: {target.name}')
if not outputs:raise ExtractionError('역할은 식별했지만 교각/좌표 데이터를 추출하지 못했습니다. 파일판독내역을 확인하세요.')
return destdir,outputs,inventory
@@ -0,0 +1,31 @@
import argparse
import json
import sys
from .api import extract_folder,check_environment
from .core import ExtractionError
def main(argv=None):
parser=argparse.ArgumentParser(description='Offline DWG bridge extractor (JSON stdout, progress stderr)')
parser.add_argument('folder',nargs='?')
parser.add_argument('--output',help='Result directory (default: input/_도면추출결과)')
parser.add_argument('--converter',help='LibreDWG dwgread executable path')
parser.add_argument('--check',action='store_true',help='Check runtime dependencies only')
parser.add_argument('--include-data',action='store_true',help='Include all cell data in JSON stdout')
args=parser.parse_args(argv)
if hasattr(sys.stdout,'reconfigure'):sys.stdout.reconfigure(encoding='utf-8')
if hasattr(sys.stderr,'reconfigure'):sys.stderr.reconfigure(encoding='utf-8')
if args.check:
result=check_environment(converter_path=args.converter)
print(json.dumps(result,ensure_ascii=False))
return 0 if result['converter_available'] else 2
if not args.folder:parser.error('folder is required unless --check is used')
try:
result=extract_folder(args.folder,output_dir=args.output,converter_path=args.converter,
on_progress=lambda msg:print(msg,file=sys.stderr,flush=True))
print(json.dumps(result.to_dict(include_data=args.include_data),ensure_ascii=False))
return 0
except ExtractionError as exc:
print(json.dumps(dict(schema_version='1.0',status='error',error=str(exc)),ensure_ascii=False))
return 1
if __name__=='__main__':raise SystemExit(main())
@@ -0,0 +1,284 @@
"""Offline DWG extraction. No AI/network calls and no use of previous output values."""
from __future__ import annotations
import csv
import hashlib
import json
import math
import os
import shutil
from pathlib import Path
import re
import subprocess
import tempfile
from .geometry import scene
from datetime import datetime
APP = Path(__file__).resolve().parent
ROLES = {'01': '종평면도', '02': '받침배치도', '04': '교각일반도'}
ROLES['03'] = 'ELEVATION 산출근거도'
HEADERS = {
'Pier': ['교각명', '교각형식', '두부보\nEL_m', '기초 하단\nEL_m', '지반고\nEL_m', '기초 형식', '말뚝\n길이_mm', '두부보\n폭_mm', '두부보\n길이_mm', '기둥갯수'],
'IP': ['교각명', 'IP점', 'x_m', 'y_m', '코핑 꺾임각'],
'Bearing': ['교각명', '받침번호', 'x_m', 'y_m', 'z_m', '방위각', '전열/후열'],
}
class ExtractionError(ValueError):
pass
def read_json(path):
return json.loads(Path(path).read_text(encoding='utf-8-sig'))
def write_json(path, data):
Path(path).write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding='utf-8')
def sha256(path):
h = hashlib.sha256()
with Path(path).open('rb') as f:
for chunk in iter(lambda: f.read(1024 * 1024), b''):
h.update(chunk)
return h.hexdigest()
def converter_path(explicit=None):
# Explicit configuration is authoritative: a typo must not select another binary.
configured = explicit or os.environ.get('BRIDGE_DWGREAD')
if configured:
candidate = Path(configured).expanduser()
if candidate.is_file():
return candidate.resolve()
raise ExtractionError(f'Configured DWG converter does not exist: {configured}')
found = shutil.which('dwgread') or shutil.which('dwgread.exe')
if found:
return Path(found).resolve()
raise ExtractionError('DWG converter not found. Pass converter_path or set BRIDGE_DWGREAD / PATH.')
def compact(raw):
if not isinstance(raw.get('OBJECTS'), list):
raise ExtractionError('변환 결과에 DWG 객체 목록이 없습니다.')
texts = []
for e in raw['OBJECTS']:
if e.get('entity') not in ('TEXT','MTEXT','ATTRIB','ATTDEF','DIMENSION_LINEAR','DIMENSION_ALIGNED'):
continue
t = e.get('text_value', e.get('text', ''))
p = e.get('ins_pt', e.get('text_midpt', [0,0]))
if not isinstance(t, str) or not t.strip():
continue
texts.append(dict(h=e['handle'][-1], x=p[0], y=p[1], t=t))
if not texts:
raise ExtractionError('추출 가능한 CAD 문자가 없습니다. 스캔 이미지 도면은 지원하지 않습니다.')
return texts
def read_cad_json(path):
"""Recover independently valid text entities if LibreDWG emits invalid geometry JSON."""
raw=Path(path).read_text(encoding='utf-8',errors='replace')
warnings=[]
if '\ufffd' in raw:warnings.append('변환기의 잘못된 UTF-8 문자를 대체함; 해당 문자는 검토 필요')
try:return json.loads(raw),warnings
except json.JSONDecodeError:
decoder=json.JSONDecoder();objects=[];failed=0
for m in re.finditer(r'(?m)^ \{\s*\n\s*"(?:entity|object)":\s*"[^\"]+"',raw):
try:
obj,_=decoder.raw_decode(raw,m.start()+4)
if '\ufffd' in obj.get('text_value',obj.get('text','')):failed+=1;continue
objects.append(obj)
except (json.JSONDecodeError,TypeError):failed+=1
if not objects:raise ExtractionError('변환 JSON 오류 및 복구 가능한 CAD 문자 없음')
warnings.append(f'변환 JSON 오류: 독립 CAD 객체 {len(objects)}개 복구, 손상 객체 {failed}개 제외')
return {'OBJECTS':objects},warnings
def convert(source, cache, exe, log=lambda s: None):
source, cache = Path(source), Path(cache)
if source.suffix.lower() != '.dwg' or not source.is_file():
raise ExtractionError(f'DWG 파일을 확인하세요: {source}')
digest = sha256(source)
# Both input content and converter version participate in the cache identity.
key = digest + '-' + sha256(exe)[:12]
cache.mkdir(parents=True, exist_ok=True)
dest = cache/(key+'.json')
if dest.exists():
try:
data = read_json(dest)
if data.get('scene_version')==3 and data['sha256']==digest and isinstance(data['texts'],list) and data['texts']:
log(f'{source.name}: 변경 없음, 캐시 사용')
return data
except (ValueError, KeyError):
pass
log(f'{source.name}: DWG 문자 읽는 중')
with tempfile.TemporaryDirectory(prefix='dwg_', dir=cache) as tmp:
raw = Path(tmp)/'raw.json'
try:
p = subprocess.run([str(exe), '-O', 'JSON', '-o', str(raw), str(source.resolve())],
capture_output=True, timeout=180, creationflags=getattr(subprocess,'CREATE_NO_WINDOW',0))
except subprocess.TimeoutExpired as e:
raise ExtractionError(f'DWG 변환 시간 초과: {source.name}') from e
if p.returncode != 0 or not raw.exists():
message = p.stderr.decode('utf8', errors='replace')[-1200:]
raise ExtractionError(f'DWG 변환 실패: {source.name}\n{message}')
decoded,warnings=read_cad_json(raw)
texts,segments,scene_warnings=scene(decoded)
if not texts:raise ExtractionError('표시 공간에 추출 가능한 CAD 문자가 없습니다.')
data = dict(sha256=digest,texts=texts,segments=segments,scene_version=3,conversion_warnings=warnings+scene_warnings)
if sha256(source)!=digest:
raise ExtractionError('읽는 동안 원본 도면이 변경되었습니다. 저장 후 다시 실행하세요.')
staging = Path(tmp)/'compact.json'
write_json(staging, data)
os.replace(staging, dest)
return data
def load_profile(path):
p = read_json(path)
if p.get('version') != 1 or not p.get('rules') or set(p.get('sources',{})) != set(ROLES):
raise ExtractionError('지원하지 않는 추출 규칙 파일입니다.')
seen = set()
for r in p['rules']:
sheet, cell = r.get('sheet'), r.get('cell','')
if sheet not in HEADERS or not re.fullmatch(r'[B-K][1-9]\d{0,4}',cell):
raise ExtractionError(f'대상 셀 오류: {sheet}!{cell}')
if int(re.search(r'\d+',cell)[0])<3 or ord(cell[0])-66>=len(HEADERS[sheet]):
raise ExtractionError(f'입력 표 밖 셀: {sheet}!{cell}')
if (sheet,cell) in seen:
raise ExtractionError(f'중복 규칙: {sheet}!{cell}')
seen.add((sheet,cell))
if r.get('op') not in ['label','manual','missing','number','min','agree','range','text']:
raise ExtractionError(f'지원하지 않는 연산: {r.get("op")}')
if r['op'] not in ['label','manual','missing'] and not r.get('refs'):
raise ExtractionError(f'추출 위치 없음: {sheet}!{cell}')
if r['op']=='manual' and r.get('doc') not in ROLES:
raise ExtractionError('등록값의 근거 도면이 필요합니다.')
for ref in r.get('refs',[])+r.get('guards',[]):
if ref.get('doc') not in ROLES:
raise ExtractionError('알 수 없는 근거 도면 역할입니다.')
rx=re.compile(ref['pattern'])
if not 0<=int(ref.get('group',1))<=rx.groups:
raise ExtractionError('정규식 그룹 번호가 맞지 않습니다.')
return p
def source_files(profile, folder, overrides=None):
folder=Path(folder); overrides=overrides or {}; found={}
for doc,s in profile['sources'].items():
if overrides.get(doc):p=Path(overrides[doc])
else:
# No recursive guessing; explicitly selected folder only.
matches=[f for f in folder.iterdir() if f.is_file() and f.name.casefold()==s['filename'].casefold()]
if len(matches)!=1:
raise ExtractionError(f'{ROLES[doc]} 파일을 선택하세요: {s["filename"]}')
p=matches[0]
if not p.is_file() or p.suffix.lower()!='.dwg':
raise ExtractionError(f'{ROLES[doc]} DWG가 없습니다: {p}')
found[doc]=p.resolve()
if len(set(found.values()))!=3:
raise ExtractionError('서로 다른 종평면도·받침배치도·교각일반도 3개를 선택하세요.')
return found
def resolve(ref, documents):
texts=documents[ref['doc']]['texts']
# Handle is validated against geometry and text grammar, never trusted alone.
spatial=[e for e in texts if abs(e['x']-ref['x'])<0.01 and abs(e['y']-ref['y'])<0.01]
matches=[e for e in spatial if re.fullmatch(ref['pattern'],e['t'])]
exact=[e for e in matches if e['h']==ref['handle']]
if len(exact)==1:
entity=exact[0]
elif len(matches)==1:
entity=matches[0]
else:
raise ExtractionError(f"문자 위치 식별 실패({len(matches)}개): {ROLES[ref['doc']]} handle {ref['handle']:X}")
value=re.fullmatch(ref['pattern'],entity['t']).group(ref.get('group',1))
return value,dict(doc=ref['doc'],**entity)
def evaluate(rule, documents, changed):
op=rule['op']; evidence=[]; status='추출'; messages=[]
if op=='label':return rule.get('value'),'식별자',[],[]
if op=='missing':return None,'미확인',[rule.get('note','추출 규칙 미등록')],[]
if op=='manual':
if rule['doc'] in changed:
return None,'재검토',['도면 변경: 형상 판독 등록값을 자동 적용하지 않았습니다.'],[]
return rule.get('value'),'등록값',[rule.get('note','등록 시 형상 판독값')],[]
for ref in rule.get('guards',[]):
_,e=resolve(ref,documents);evidence.append(e)
values=[]
for ref in rule['refs']:
value,e=resolve(ref,documents);values.append(value);evidence.append(e)
if rule.get('pile_minimum'):
from .semantic import pile_minimum_m
lengths=[pile_minimum_m(v) for v in values]
value=min(lengths) if lengths and None not in lengths else None
status='산정값' if value is not None else '미확인'
messages.append('말뚝길이 표기 최솟값 선택: '+' / '.join(values))
elif op=='text':value=values[0].strip()
elif op=='range':
value=values[0].replace(' ','')
if '~' in value:status='검토필요';messages.append('도면 범위 원문. 단일 모델 입력 길이는 미확정.')
else:value=float(value)
else:
nums=[float(v) for v in values]
if not all(math.isfinite(v) for v in nums):raise ExtractionError('유한 숫자가 아닙니다.')
if op=='agree' and max(nums)-min(nums)>1e-8:
value=None;status='미확정';messages.append('도면 표기 상충: '+' / '.join(map(str,nums)))
else:
value=(min(nums) if op=='min' else nums[0])*rule.get('scale',1)
if rule.get('scale')==1000:value=round(value,6)
if rule.get('review'):messages.append(rule['review']);status='검토필요'
if any(e['doc'] in changed for e in evidence):
messages.append('등록 후 도면 변경됨. 동일 위치에서 재추출한 값의 의미를 확인하세요.')
if status=='추출':status='재검토'
return value,status,messages,evidence
def extract(profile, documents):
changed={d for d in documents if documents[d]['sha256']!=profile['sources'][d]['sha256']}
cells={s:{} for s in HEADERS};records=[]
for rule in profile['rules']:
try:value,status,messages,evidence=evaluate(rule,documents,changed)
except (ExtractionError,ValueError,IndexError) as e:
value,status,messages,evidence=None,'추출실패',[str(e)],[]
cells[rule['sheet']][rule['cell']]=value
records.append(dict(sheet=rule['sheet'],cell=rule['cell'],value=value,status=status,
messages=messages,note=rule.get('note',''),evidence=evidence))
# Every labeled record must have all non-optional fields registered.
for sheet,values in cells.items():
rows={int(c[1:]) for c in values if c.startswith('B') and values[c] is not None}
for row in sorted(rows):
for col in range(2,2+len(HEADERS[sheet])):
cell=f'{chr(64+col)}{row}'
if cell not in values:
values[cell]=None
records.append(dict(sheet=sheet,cell=cell,value=None,status='미확인',messages=['필드 추출 규칙 미등록'],note='',evidence=[]))
return dict(name=profile['name'],profile=profile['id'],cells=cells,records=records,
changed_sources=sorted(changed),notes=profile.get('notes',[]))
def run(profile_path, folder, output, converter=None, overrides=None, log=lambda s:None):
from .workbook import export_workbook
profile=load_profile(profile_path)
paths=source_files(profile,folder,overrides)
exe=converter_path(converter)
output=Path(output);output.mkdir(parents=True,exist_ok=True)
docs={d:convert(p,output/'.cache',exe,log) for d,p in paths.items()}
result=extract(profile,docs)
result['sources']={d:dict(path=str(paths[d]),sha256=docs[d]['sha256']) for d in paths}
if all(r['status']=='추출실패' for r in result['records'] if r['status'] not in ['식별자','등록값','미확인']):
raise ExtractionError('이 도면은 선택한 추출 규칙과 맞지 않습니다. 새 규칙을 등록하세요.')
safe=re.sub(r'[^\w가-힣().-]+','_',profile['name'])[:45]
run_dir=output/(safe+'_'+datetime.now().strftime('%Y%m%d_%H%M%S_%f'))
run_dir.mkdir(exist_ok=False)
dest=run_dir/(safe+'_교각입력데이터.xlsx')
export_workbook(result,dest)
write_json(run_dir/'추출근거.json',result)
with (run_dir/'검토항목.csv').open('w',encoding='utf-8-sig',newline='') as f:
w=csv.writer(f);w.writerow(['시트','셀','상태','값','사유'])
for r in result['records']:
if r['status'] not in ['추출','식별자','등록값']:
w.writerow([r['sheet'],r['cell'],r['status'],r['value'], ' / '.join(r['messages'])])
log(f'완료: {dest}')
return dest,result
def index_files(paths,output,converter=None,log=lambda s:None):
"""Export searchable CAD text for offline, code-free CSV mapping registration."""
output=Path(output);output.mkdir(parents=True,exist_ok=True);exe=converter_path(converter)
manifest={}
for doc,p in paths.items():
data=convert(p,output/'.cache',exe,log)
manifest[doc]=dict(filename=Path(p).name,sha256=data['sha256'],texts=data['texts'])
with (output/f'{doc}_문자목록.tsv').open('w',encoding='utf-8-sig',newline='') as f:
w=csv.writer(f,delimiter='\t');w.writerow(['handle(10진수)','도면X','도면Y','문자'])
for e in data['texts']:w.writerow([e['h'],e['x'],e['y'],e['t']])
write_json(output/'문자색인.json',manifest)
return output/'문자색인.json'
@@ -0,0 +1,122 @@
"""Offline 2-D block expansion and bounded line connectivity for CAD annotations."""
import math
import bisect
class LineIndex:
def __init__(self,segments):
self.segments=segments or []
self.horizontal=sorted((s['a'][1],i) for i,s in enumerate(self.segments) if abs(s['a'][1]-s['b'][1])<1e-7)
self.ys=[v[0] for v in self.horizontal]
lengths=sorted(math.dist(s['a'],s['b']) for s in self.segments)
self.size=max(lengths[len(lengths)//2]*10,1e-6) if lengths else 1
self.grid={};self.long=[]
for i,s in enumerate(self.segments):
x1,x2=sorted(math.floor(p[0]/self.size) for p in [s['a'],s['b']]);y1,y2=sorted(math.floor(p[1]/self.size) for p in [s['a'],s['b']])
if (x2-x1+1)*(y2-y1+1)>100:self.long.append(i);continue
for x in range(x1,x2+1):
for y in range(y1,y2+1):self.grid.setdefault((x,y),[]).append(i)
def near(self,x,y,r):
x1,x2=math.floor((x-r)/self.size),math.floor((x+r)/self.size);y1,y2=math.floor((y-r)/self.size),math.floor((y+r)/self.size)
if (x2-x1+1)*(y2-y1+1)>10000:return self.segments
ids=set(self.long)
for ix in range(x1,x2+1):
for iy in range(y1,y2+1):ids.update(self.grid.get((ix,iy),[]))
return [self.segments[i] for i in ids]
def horizontal_near(self,y,r):
return [self.segments[i] for _,i in self.horizontal[bisect.bisect_left(self.ys,y-r):bisect.bisect_right(self.ys,y+r)]]
IDENTITY=(1,0,0,1,0,0)
def transform(m,p):
a,b,c,d,x,y=m
return [a*p[0]+c*p[1]+x,b*p[0]+d*p[1]+y]
def compose(a,b):
p=transform(a,[b[4],b[5]])
return (a[0]*b[0]+a[2]*b[1],a[1]*b[0]+a[3]*b[1],a[0]*b[2]+a[2]*b[3],a[1]*b[2]+a[3]*b[3],*p)
def handle(obj):return obj.get('handle',[0])[-1]
def reference(value):return value[-1] if isinstance(value,list) and value else None
def scene(raw):
objects=raw.get('OBJECTS',[]);lookup={handle(e):e for e in objects}
headers={handle(e):e for e in objects if e.get('object')=='BLOCK_HEADER'}
child_handles={reference(ref) for h in headers.values() if not h.get('name','').upper().startswith(('*MODEL_SPACE','*PAPER_SPACE')) for ref in h.get('entities',[])}
roots=[h for h in headers.values() if h.get('name','').upper().startswith(('*MODEL_SPACE','*PAPER_SPACE'))]
texts=[];segments=[];warnings=[]
def visit(e,m,chain):
kind=e.get('entity');h=handle(e)
if e.get('invisible'):return
if kind=='INSERT':
bh=reference(e.get('block_header'));block=headers.get(bh)
if not block or bh in chain or len(chain)>=16:
warnings.append(f'블록 전개 불가: {h:X}');return
if e.get('extrusion',[0,0,1])!=[0,0,1]:
warnings.append(f'비평면 블록 미지원: {h:X}');return
angle=e.get('rotation',0);scale=e.get('scale',[1,1,1]);p=e.get('ins_pt',[0,0]);base=block.get('base_pt',[0,0])
c,s=math.cos(angle),math.sin(angle)
local=(c*scale[0],s*scale[0],-s*scale[1],c*scale[1],0,0)
offset=transform(local,base);local=(*local[:4],p[0]-offset[0],p[1]-offset[1])
for ref in block.get('entities',[]):
child=lookup.get(reference(ref))
if child:visit(child,compose(m,local),(*chain,bh))
return
if kind in ('TEXT','MTEXT','ATTRIB','ATTDEF','DIMENSION_LINEAR','DIMENSION_ALIGNED'):
if kind=='ATTDEF':return
t=e.get('text_value',e.get('text',''));p=e.get('ins_pt',e.get('text_midpt'))
if isinstance(t,str) and t.strip() and p:
p=transform(m,p)
if all(math.isfinite(v) for v in p):texts.append(dict(h=h,x=p[0],y=p[1],t=t,height=abs(e.get('height',e.get('text_height',0)))*math.hypot(m[0],m[1]),block_path=list(chain)))
pairs=[]
if kind=='LINE':pairs=[(e.get('start'),e.get('end'))]
elif kind in ('LWPOLYLINE','LEADER'):
points=e.get('points',[]);pairs=list(zip(points,points[1:]))
if kind=='LWPOLYLINE' and e.get('flag',0)&1 and len(points)>2:pairs.append((points[-1],points[0]))
for a,b in pairs:
if a is None or b is None:continue
a,b=transform(m,a),transform(m,b)
if all(math.isfinite(v) for v in a+b) and a!=b:segments.append(dict(h=h,a=a,b=b,layer=reference(e.get('layer')),block_path=list(chain)))
if roots and all('entities' in h for h in roots):
ids={reference(ref) for root in roots for ref in root['entities']}
root_handles={handle(root) for root in roots}
# Older DWG versions encode space membership via entmode/owner, not entity arrays.
ids.update(handle(e) for e in objects if e.get('entity') and (e.get('entmode') in (1,2) or reference(e.get('ownerhandle')) in root_handles) and handle(e) not in child_handles)
# ATTRIB coordinates are stored in drawing space by LibreDWG.
ids.update(handle(e) for e in objects if e.get('entity')=='ATTRIB' and handle(e) not in child_handles)
for h in ids:
if h in lookup:visit(lookup[h],IDENTITY,())
else:
for e in objects:
if handle(e) not in child_handles:visit(e,IDENTITY,())
return texts,segments,list(dict.fromkeys(warnings))
def leader_endpoint(text,segments):
"""Follow a short horizontal landing plus connected leader, never a dense grid."""
height=text.get('height',0)
if height<=0:return None
x,y=text['x'],text['y'];tol=height*.15
landing=[]
for seg in segments:
a,b=seg['a'],seg['b']
if abs(a[1]-b[1])<tol and min(a[0],b[0])-height<x<max(a[0],b[0])+height and abs(a[1]-y)<height*1.8 and height<math.dist(a,b)<height*35:landing.append(seg)
if len(landing)!=1:return None
start=landing[0];tips=[]
for endpoint in [start['a'],start['b']]:
current=endpoint;used={id(start)};path=[start]
for _ in range(4):
nexts=[s for s in segments if id(s) not in used and min(math.dist(current,s['a']),math.dist(current,s['b']))<tol and math.dist(s['a'],s['b'])<height*60]
if not nexts:
if len(path)>1:tips.append((current,path))
break
if len(nexts)!=1:break
seg=nexts[0];used.add(id(seg));path=path+[seg]
current=seg['b'] if math.dist(current,seg['a'])<tol else seg['a']
return tips[0] if len(tips)==1 else None
def table_row_band(point,segments,span):
"""Use real horizontal cell borders; reject oversized/ambiguous enclosures."""
ys=[]
for s in (segments.horizontal_near(point['y'],span) if isinstance(segments,LineIndex) else segments or []):
a,b=s['a'],s['b']
if abs(a[1]-b[1])<span*1e-5 and min(a[0],b[0])<point['x']<max(a[0],b[0]) and abs(a[1]-point['y'])<span:ys.append(a[1])
lower=[y for y in ys if y<point['y']];upper=[y for y in ys if y>point['y']]
if not lower or not upper:return None
lo,hi=max(lower),min(upper)
return (lo,hi) if span*.02<hi-lo<span else None
@@ -0,0 +1,165 @@
"""Conservative drawing-only associations; no workbook or sample-name dependencies."""
import math
import re
from .semantic import norm, distance, pile_minimum_m, angle_text
from .geometry import leader_endpoint,LineIndex
def coordinate_points(texts):
points=[]
for e in texts:
x=re.search(r'X\s*[=:]\s*([-+]?\d+(?:\.\d+)?)',e['t'],re.I)
if not x:continue
y=re.search(r'Y\s*[=:]\s*([-+]?\d+(?:\.\d+)?)',e['t'],re.I);ye=e
if not y:
candidates=[a for a in texts if a['y']<e['y'] and abs(a['x']-e['x'])<max(.01,(e['y']-a['y'])*.05) and re.search(r'Y\s*[=:]',a['t'],re.I)]
if candidates:
ye=min(candidates,key=lambda a:distance(a,e));y=re.search(r'Y\s*[=:]\s*([-+]?\d+(?:\.\d+)?)',ye['t'],re.I)
if y:points.append(dict(x=float(x[1]),y=float(y[1]),cad=e,evidence=[e,ye]))
return points
def associate_ips(points,bearing_rows):
"""Require exactly three unique points near an unambiguous bearing footprint."""
footprints={}
for p in {r['p'] for r in bearing_rows}:
rows=[r for r in bearing_rows if r['p']==p]
xy={(r['x'],r['y']) for r in rows}
# Conflicting coordinates for one bearing indicate overlapping carriageways.
if any(len({(r['x'],r['y']) for r in rows if r['no']==no})>1 for no in {r['no'] for r in rows}):continue
if len(xy)<2:continue
center=tuple(sum(v[k] for v in xy)/len(xy) for k in [0,1])
radius=max(math.dist(center,v) for v in xy)
# IP callouts often sit outside the outermost bearing, near cap edges.
# Competing-pier and exact-three checks below still reject ambiguous groups.
footprints[p]=(center,max(radius*2.5,2.0))
grouped={p:{} for p in footprints}
for point in points:
xy=(point['x'],point['y'])
candidates=sorted((math.dist(xy,c),p,limit) for p,(c,limit) in footprints.items())
if not candidates:continue
d,p,limit=candidates[0]
if d>limit or len(candidates)>1 and candidates[1][0]<max(d*1.5,d+1):continue
grouped[p].setdefault(xy,point)
return {p:sorted(ps.values(),key=lambda a:(-a['cad']['y'],a['cad']['x'])) for p,ps in grouped.items() if len(ps)==3}
def profile_fields(texts,segments=None):
"""Local explicit EL/GL and pile labels only. Unlabelled terrain tables stay unresolved."""
labels=[e for e in texts if re.fullmatch(r'P\d+',norm(e['t']))]
result=[];connections={};line_index=segments if isinstance(segments,LineIndex) else LineIndex(segments)
for label in labels:
peers=[e for e in labels if norm(e['t'])!=norm(label['t']) and abs(e['y']-label['y'])<abs(e['x']-label['x'])*.25]
if not peers:continue
span=min(abs(e['x']-label['x']) for e in peers)
if span<=0:continue
if any(norm(e['t'])!=norm(label['t']) and abs(e['x']-label['x'])<span*.03 and 0<abs(e['y']-label['y'])<span*.25 for e in labels):continue
local=[]
for e in texts:
pos=(e['x'],e['y']);connection=None
if segments and re.match(r'(?:E\.?L\.?|G\.?L\.?)',norm(e['t'])):
key=(e['h'],e['x'],e['y'])
if key not in connections:
reach=max(e.get('height',0)*100,span)
nearby=line_index.near(e['x'],e['y'],reach)
connections[key]=leader_endpoint(e,nearby)
connection=connections[key]
if connection:pos=connection[0]
if abs(pos[0]-label['x'])<span*.42 and abs(pos[1]-label['y'])<span*1.2:
if connection:e=dict(e,leader_tip=pos,leader_handles=[s['h'] for s in connection[1]])
local.append(e)
def record(col,value,ev):result.append(dict(p=norm(label['t']),col=col,value=value,inferred=True,evidence=[label,*ev]))
els=[]
for e in local:
m=re.fullmatch(r'(?:E\.?L\.?)[=:]*([-+]?\d+(?:\.\d+)?)(?:\([좌우]\))?',norm(e['t']))
if m and abs(e['y']-label['y'])>span*.05:els.append((float(m[1]),e))
gl=re.fullmatch(r'(?:G\.?L\.?|지반고(?:EL)?)[=:]*([-+]?\d+(?:\.\d+)?)',norm(e['t']))
if gl:record('F',float(gl[1]),[e])
ground_heads=[e for e in texts if norm(e['t']) in ['지반고','지반고EL','GROUNDLEVEL'] and e['y']<label['y'] and 0<label['y']-e['y']<span*3 and e['x']<label['x']]
if ground_heads:
head=min(ground_heads,key=lambda e:abs(e['y']-label['y']))
band=max(head.get('height',0)*2,span*.06)
row_values=[e for e in texts if abs(e['y']-head['y'])<band and head['x']<e['x'] and re.fullmatch(r'[-+]?\d+\.\d+',e['t'].strip())]
# Regular station samples are not the pier station. Prefer inserted pier ordinates.
xs=sorted(set(e['x'] for e in row_values));pitches={}
for a,b in zip(xs,xs[1:]):
step=b-a
if span*.15<step<span:
key=round(step/(span*.002));pitches[key]=pitches.get(key,0)+1
if pitches:
pitch=max(pitches,key=pitches.get)*span*.002
phases=[(sum(abs((x-a)/pitch-round((x-a)/pitch))<.025 for x in xs),a) for a in xs]
count,origin=max(phases,default=(0,0))
if count>=4:row_values=[e for e in row_values if abs((e['x']-origin)/pitch-round((e['x']-origin)/pitch))>=.025]
ground=[e for e in row_values if abs(e['x']-label['x'])<span*.15]
if ground:
nearest=min(ground,key=lambda e:abs(e['x']-label['x']))
equally_close=[e for e in ground if abs(abs(e['x']-label['x'])-abs(nearest['x']-label['x']))<span*.001]
for e in equally_close:record('F',float(e['t']),[head,e])
# Multiple EL leader levels cannot safely be identified as foundation bottom.
# The lowest EL leader group in a pier's profile column belongs to its base.
# Retain paired left/right labels as separate candidates rather than choosing a value.
if els:
bottom=min(e.get('leader_tip',[e['x'],e['y']])[1] for _,e in els)
els=[(v,e) for v,e in els if e.get('leader_tip',[e['x'],e['y']])[1]-bottom<span*.10]
local_foundation=bool(els)
if local_foundation:
for value,e in els:record('E',value,[e])
# A plan-view P label must not steal an abutment or another section's pile label.
foundation_texts=[e for e in local if local_foundation and min(distance(e,a) for _,a in els)<span*.65]
piles=[e for e in foundation_texts if '말뚝' in e['t'] and len(e['t'])<60]
direct=[e for e in foundation_texts if any(t in norm(e['t']) for t in ['직접기초','확대기초'])]
if piles and not direct:
record('G','현장타설말뚝' if any('현장타설' in e['t'] for e in piles) else '말뚝기초',piles)
lengths=[]
for e in local:
m=re.search(r'(?<![A-Z.])L\s*=\s*(\d+(?:\.\d+)?(?:\s*[~~]\s*\d+(?:\.\d+)?)?)\s*(MM|M)\b',e['t'].replace('\\P',' '),re.I)
if m and min(distance(e,a) for a in piles)<span*.35:
lengths.append((pile_minimum_m(m[1]+m[2]),e))
if lengths:record('H',min(v for v,e in lengths),[e for v,e in lengths])
elif direct and not piles:
record('G','직접기초',direct);record('H',0,direct)
types=[]
for e in local:
t=norm(e['t'])
if re.search(r'(?:기둥(?:개수|수)?[:=]?2|2주식|Π형)',t):types.append(('π형',e))
elif re.search(r'(?:기둥(?:개수|수)?[:=]?1|1주식|T형)',t):types.append(('T형',e))
for value,e in types:record('C',value,[e])
return result
def scalar(text):
t=re.sub(r'\s+','',text)
m=re.fullmatch(r'(?:E\.?L\.?[=:]*)?([-+]?\d+(?:\.\d+)?)(?:\([좌우]\))?',t,re.I)
return float(m[1]) if m else None
def integrated_bearings(ts,erows):
out=[]
for row in erows:
no=next(e for e in row['evidence'] if re.fullmatch(r'\d+',e['t']) and int(e['t'])==row['no'])
xheads=[e for e in ts if norm(e['t'])=='X좌표' and e['y']>no['y'] and e['x']>no['x']]
if not xheads:continue
xh=min(xheads,key=lambda e:distance(e,no))
yheads=[e for e in ts if norm(e['t'])=='Y좌표' and abs(e['y']-xh['y'])<max(abs(e['x']-xh['x'])*.001,1e-6)]
if not yheads:continue
yh=min(yheads,key=lambda e:distance(e,xh));span=abs(yh['x']-xh['x'])
if not span or abs(no['x']-xh['x'])>span*4 or xh['y']-no['y']>span*12:continue
same=[e for e in ts if abs(e['y']-no['y'])<span*.001]
vals={};ev=list(row['evidence'])
for field,title in [('x','X좌표'),('y','Y좌표'),('angle','일반각도계')]:
heads=[e for e in ts if norm(e['t'])==title and abs(e['y']-xh['y'])<span*.001]
if not heads:continue
h=min(heads,key=lambda e:distance(e,xh))
cand=[e for e in same if abs(e['x']-h['x'])<span*.4 and (scalar(e['t']) is not None if field!='angle' else '°' in angle_text(e['t']))]
if cand:
e=min(cand,key=lambda e:abs(e['x']-h['x']));vals[field]=scalar(e['t']) if field!='angle' else angle_text(e['t']);ev.extend([h,e])
sta=[e for e in same if 'STA.' in e['t'] and 0<e['x']-xh['x']<span*4]
station=None
if sta:
sm=re.search(r'(\d+)K\+([\d.]+)',sta[0]['t']);station=int(sm[1])*1000+float(sm[2]) if sm else None;ev+=sta
if 'x' in vals and 'y' in vals:out.append(dict(p=row['p'],no=row['no'],row_type=None,station=station,evidence=ev,**vals))
for p in {r['p'] for r in out}:
rs=[r for r in out if r['p']==p and r['station'] is not None]
ss=sorted(set(r['station'] for r in rs))
if len(ss)>1:
gaps=[(b-a,(a+b)/2) for a,b in zip(ss,ss[1:])];gap,mid=max(gaps)
if gap>.5:
for r in rs:r['row_type']='전열' if r['station']<mid else '후열'
return out
@@ -0,0 +1,285 @@
"""Content/relative-layout readers. No fixed handles, file names or absolute positions."""
import collections
import math
import re
NUMBER=r'[-+]?\d+(?:\.\d+)?'
def pile_minimum_m(value):
"""Parse a length or range; unqualified drawing lengths are metres."""
if isinstance(value,(int,float)):
return float(value) if math.isfinite(value) and value>=0 else None
t=re.sub(r'\s+','',str(value)).upper()
t=re.sub(r'^L[=:]?','',t)
unit=.001 if t.endswith('MM') else 1
t=re.sub(r'MM$|M$','',t)
if not re.fullmatch(r'\d+(?:\.\d+)?(?:[~~〜–/,-]\d+(?:\.\d+)?)*',t):return None
return min(map(float,re.findall(r'\d+(?:\.\d+)?',t)))*unit
def norm(t):return re.sub(r'\s+','',t).upper()
def distance(a,b):return math.hypot(a['x']-b['x'],a['y']-b['y'])
def unique_entities(items,tolerance):
out=[]
for e in items:
if not any(norm(a['t'])==norm(e['t']) and distance(a,e)<tolerance for a in out):out.append(e)
return out
def angle_text(t):return re.sub(r'%%[dD]','°',t).strip()
def pier(text):
m=re.search(r'(?<![A-Z])P\s*\.?\s*(\d+)',text.upper())
return 'P'+str(int(m[1])) if m else None
def classify(texts):
ts=[norm(e['t']) for e in texts];roles=[]
# File paths are deliberately absent. Titles are hints; tables also identify roles.
if (any(axis_header(t,'X') for t in ts) and any(axis_header(t,'Y') for t in ts)) or any(drawing_title(t,'bearing') for t in ts):roles.append('02')
if any(drawing_title(t,'pier') for t in ts):roles.append('04')
if any(t=='지반고' for t in ts) and any(t in ['계획고','측점'] for t in ts):roles.append('01')
# Geological/structural sheets also contain local "종단면도 (A-A)" views.
# Such section titles alone do not identify the bridge longitudinal schedule.
if any(drawing_title(t,'profile') and ('종평면도' in t or any(v in ts for v in ['측점','계획고','STATION'])) for t in ts) and '01' not in roles:roles.append('01')
if any(t in ['교각명','교각번호'] for t in ts):
if any('두부보' in t or '코핑' in t for t in ts) and '04' not in roles:roles.append('04')
if any('기초하단' in t or '말뚝길이' in t for t in ts) and '01' not in roles:roles.append('01')
if any(drawing_title(t,'elevation') for t in ts) or ('ELEVATION' in ts and 'STATION' in ts) or ('교좌면EL' in ts and any('몰탈상단' in t for t in ts)):roles.append('03')
# An EL-X table with a pier identifier is stronger evidence than an exact title.
# Calculation sheets remain ancillary and cannot supply authoritative bearing Z.
if '04' not in roles and '03' not in roles and any(elx_header(t) for t in ts) and any(pier(t) for t in ts) and any(t in ['NO.','NO','번호'] for t in ts):roles.append('04')
return roles
def drawing_title(text,kind):
t=norm(text)
if len(t)>90 or any(w in t for w in ['참조','참고','따른다','SEE']):return False
patterns={
'pier':r'교각(?:일반도|구조일반도|구조도|도)(?:\([^)]*\)|[-_\d])*$',
'bearing':r'(?:교량)?받침(?:배치도|배치계획도)(?:\([^)]*\)|[-_\d])*$',
'profile':r'(?:종평면도|종단면도|종단도)(?:\([^)]*\)|[-_\d])*$',
'elevation':r'(?:ELEVATION|표고)(?:산출|계산)(?:근거)?(?:도|표)(?:\([^)]*\)|[-_\d])*$',
}
return bool(re.search(patterns[kind],t))
def axis_header(text,axis):
t=norm(text).replace('_','')
return t in {axis+'좌표',axis+'(M)',axis+'COORDINATE',axis+'좌표(M)',axis+'좌표M'}
def elx_header(text):
return re.sub(r'''[.′’‘'"“”]''','',norm(text))=='ELX'
ALIASES={
'C':{'교각형식','교각형태'},'D':{'두부보EL','두부보상단EL','코핑EL'},
'E':{'기초하단EL','기초저면EL'},'F':{'지반고','지반고EL'},
'G':{'기초형식'},'H':{'말뚝길이'},'I':{'두부보폭','코핑폭'},
'J':{'두부보길이','코핑길이'},'K':{'기둥갯수','기둥개수','기둥수'},
}
def header_field(t):
t=re.sub(r'[_\s]|\((?:MM|M)\)','',norm(t))
t=re.sub(r'(?:MM|M)$','',t)
return next((col for col,labels in ALIASES.items() if t in labels),None)
def pier_tables(texts,role):
"""Read explicitly labeled pier schedules of previously unseen layouts."""
result=[];allowed='CDIJK' if role=='04' else 'EFGH'
for ph in [e for e in texts if norm(e['t']) in ['교각명','교각번호']]:
candidates=[e for e in texts if header_field(e['t']) in set(allowed) and e['x']!=ph['x'] and abs(e['y']-ph['y'])<abs(e['x']-ph['x'])*.001]
if not candidates:continue
span=min(abs(e['x']-ph['x']) for e in candidates)
ids=sorted([e for e in texts if re.fullmatch(r'P\d+',norm(e['t'])) and abs(e['x']-ph['x'])<span*.4 and e['y']<ph['y']],key=lambda e:-e['y'])
if not ids:continue
pitch=ph['y']-ids[0]['y']
if pitch>span*2:continue
for i,label in enumerate(ids):
if i and ids[i-1]['y']-label['y']>pitch*1.8:break
for h in candidates:
es=[e for e in texts if abs(e['y']-label['y'])<pitch*.03 and abs(e['x']-h['x'])<span*.4]
if len(es)!=1:continue
e=es[0];col=header_field(h['t']);value=e['t'].strip()
if col not in 'CG':
if col=='H' and pile_minimum_m(value) is not None:
value=pile_minimum_m(value)
if 'MM' in norm(h['t']) and not re.search(r'M\s*$',e['t'],re.I):value/=1000
elif re.fullmatch(r'(?:EL[.= ]*)?'+NUMBER,value,re.I):value=float(re.search(NUMBER+r'$',value)[0])
else:continue
# Dimension headers with explicit m must be converted into mm.
if col in 'IJ' and isinstance(value,float) and re.search(r'(?:_M|\(M\))$',norm(h['t'])):value*=1000
result.append(dict(p=pier(label['t']),col=col,value=value,evidence=[ph,label,h,e]))
return result
class Index:
def __init__(self,texts):
self.texts=texts;self.by_text=collections.defaultdict(list);self.cache={}
for e in texts:self.by_text[norm(e['t'])].append(e)
def locate(self,ref):
"""Fit scale+translation from local text landmarks, then validate other landmarks."""
key=(ref['x'],ref['y'],ref['pattern'])
if key in self.cache:return self.cache[key]
anchors=[a for a in ref.get('anchors',[]) if self.by_text.get(a['t'])]
matches={}
pattern=re.compile(ref['pattern'])
if len(anchors)<3:return None
# Distinctive pier title is the first anchor when available. Do not substitute
# another pier's equally shaped table if the identifying title vanished.
original=ref.get('anchors',[])
if original and pier(original[0]['t']) and original[0] not in anchors:return None
first=anchors[0]
for second in anchors[1:5]:
dx,dy=second['x']-first['x'],second['y']-first['y'];den=dx*dx+dy*dy
if den<1e-12:continue
span=math.sqrt(den)
for a in self.by_text[first['t']]:
for b in self.by_text[second['t']]:
ux,uy=b['x']-a['x'],b['y']-a['y'];scale=(ux*dx+uy*dy)/den
if not .001<=scale<=1000:continue
tol=max(span*scale*.00002,1e-5)
if math.hypot(ux-scale*dx,uy-scale*dy)>tol:continue
tx,ty=a['x']-scale*first['x'],a['y']-scale*first['y']
confirmed=0
for anchor in anchors:
x,y=tx+scale*anchor['x'],ty+scale*anchor['y']
if any(math.hypot(e['x']-x,e['y']-y)<tol for e in self.by_text[anchor['t']]):confirmed+=1
if confirmed<3:continue
x,y=tx+scale*ref['x'],ty+scale*ref['y']
for e in self.texts:
if abs(e['x']-x)<tol and abs(e['y']-y)<tol and pattern.fullmatch(e['t']):
matches[(e['h'],e['x'],e['y'])]=(e,confirmed)
# Prefer the transformation supported by the most independent headings.
if matches:
top=max(score for _,score in matches.values())
best=[e for e,score in matches.values() if score==top]
value=best[0] if len(best)==1 else None
else:value=None
self.cache[key]=value
return value
def table_rows(texts,header,xspan,left=None):
"""Numeric column with row pitch estimated from its own first two values."""
xs=[e for e in texts if abs(e['x']-header['x'])<xspan*.45 and e['y']<header['y'] and re.fullmatch(NUMBER,e['t'])]
xs.sort(key=lambda e:-e['y'])
xs=unique_entities(xs,xspan*.001)
if not xs:return []
first=header['y']-xs[0]['y']
if first<=0 or first>xspan:return []
pitch=xs[0]['y']-xs[1]['y'] if len(xs)>1 else first
if pitch<=0 or not .4*first<pitch<2.5*first:pitch=first
result=[]
for e in xs:
if len(result)>=100:break
if result and abs((result[-1]['y']-e['y'])-pitch)>pitch*.12:break
# A new header or table title between rows terminates the current table.
if any(norm(t['t'])==norm(header['t']) and abs(t['x']-header['x'])<xspan*.3 and e['y']<=t['y']<header['y'] for t in texts):break
result.append(e)
return result
def bearings(texts,segments=None):
"""Read columns from headers even if their order/positions/row pitch change."""
out=[];issues=[]
titles=[e for e in texts if re.fullmatch(r'P\d+\([전후](?:열)?(?:,?RAMP-A교)?\)',norm(e['t']))]
for xh in [e for e in texts if axis_header(e['t'],'X')]:
# Y header nearest on the same row; position can be left or right of X.
ys=[e for e in texts if axis_header(e['t'],'Y') and abs(e['y']-xh['y'])<max(abs(e['x']-xh['x'])*.0001,1e-5)]
if not ys:continue
yh=min(ys,key=lambda e:abs(e['x']-xh['x']));span=abs(yh['x']-xh['x'])
if not span:continue
ts=[t for t in titles if 0<t['y']-xh['y']<span and abs(t['x']-xh['x'])<span*2]
if not ts:continue
title=min(ts,key=lambda t:distance(t,xh));p=pier(title['t'])
headings=[e for e in texts if abs(e['y']-xh['y'])<span*.001 and abs(e['x']-xh['x'])<span*3.5]
ah=[e for e in headings if norm(e['t']) in ['일반각도계','방위각','AZIMUTH']]
nh=[e for e in headings if norm(e['t']) in ['구분','NO.','NO','번호']]
if not nh:continue
noh=min(nh,key=lambda e:abs(e['x']-xh['x']))
next_numbers=[e['x'] for e in texts if norm(e['t']) in ['구분','NO.','NO','번호'] and abs(e['y']-xh['y'])<span*.001 and e['x']>noh['x']+span*.1]
right=min(next_numbers) if next_numbers else float('inf')
ah=[e for e in ah if noh['x']<e['x']<right]
angleh=min(ah,key=lambda e:abs(e['x']-xh['x'])) if ah else None
for x in table_rows(texts,xh,span):
from .geometry import table_row_band
band=table_row_band(x,segments,span)
same=[e for e in texts if (band[0]<e['y']<band[1] if band else abs(e['y']-x['y'])<span*.001)]
yy=[e for e in same if abs(e['x']-yh['x'])<span*.45 and re.fullmatch(NUMBER,e['t'])]
aa=[e for e in same if angleh and abs(e['x']-angleh['x'])<span*.45 and '°' in angle_text(e['t'])]
nn=[e for e in same if abs(e['x']-noh['x'])<span*.25 and re.fullmatch(r'\d+',e['t'])]
yy=unique_entities(yy,span*.001);aa=unique_entities(aa,span*.001);nn=unique_entities(nn,span*.001)
if len(yy)==len(nn)==1 and len(aa)<=1:
row_type='전열' if '(전' in norm(title['t']) else '후열'
out.append(dict(p=p,no=int(nn[0]['t']),x=float(x['t']),y=float(yy[0]['t']),angle=angle_text(aa[0]['t']) if aa else None,row_type=row_type,evidence=[title,xh,yh,nn[0],x,yy[0]]+([angleh,*aa] if angleh else [])))
else:issues.append(f'{p}: 좌표표 행 대응 모호함')
return out,issues
def elevations(texts):
out=[]
titles=[e for e in texts if pier(e['t']) and (any(t in norm(e['t']) for t in ['교각일반도','교각구조도','교각도','교각구조일반도']) or re.fullmatch(r'\(.*방향P\d+\)',norm(e['t'])) or re.fullmatch(r'P\d+(?:\([^)]*\))?',norm(e['t'])))]
for h in [e for e in texts if elx_header(e['t'])]:
numbers=[e for e in texts if norm(e['t']) in ['NO.','NO','번호'] and e['x']<h['x'] and abs(e['y']-h['y'])<abs(e['x']-h['x'])*.001]
if not numbers or not titles:continue
nh=min(numbers,key=lambda e:abs(e['x']-h['x']));span=h['x']-nh['x']
nearest=sorted([t for t in titles if t['y']>h['y']],key=lambda e:distance(e,h))
if not nearest:continue
title=nearest[0];p=pier(title['t'])
# Do not match a tiny dimension-callout EL.X to a remote table.
if distance(title,h)>span*15:continue
for z in table_rows(texts,h,span*.45):
nn=[e for e in texts if abs(e['y']-z['y'])<span*.001 and abs(e['x']-nh['x'])<span*.06 and re.fullmatch(r'\d+',e['t'])]
if len(nn)==1:out.append(dict(p=p,no=int(nn[0]['t']),z=float(z['t']),evidence=[title,h,nh,nn[0],z]))
return out
def drawing_direction(text):
m=re.match(r'^\(?([가-힣]+)방향(?:[,)]|BR|교량받침|$)',norm(text))
return m[1] if m and m[1] not in ['교축','교축직각','횡','종','양','직각'] else None
def calculation_elevations(texts,segments=None):
"""Read mortar-top and seat columns, grouping merged pier cells by NO resets.
All matching tables/revisions are retained; callers resolve conflicts explicitly.
No baseline-line elevation or general drawing EL-X is used.
"""
out=[];issues=[]
for zh in [e for e in texts if norm(e['t']) in ['교량받침EL','교량받침EL(몰탈상단)']]:
seats=[e for e in texts if norm(e['t'])=='교좌면EL' and e['x']!=zh['x'] and abs(e['y']-zh['y'])<abs(e['x']-zh['x'])*.15]
if not seats:continue
sh=min(seats,key=lambda e:distance(e,zh));span=abs(zh['x']-sh['x'])
mortar=[e for e in texts if norm(e['t'])=='(몰탈상단)' and abs(e['x']-zh['x'])<span*.5 and abs(e['y']-zh['y'])<span*.5]
if not mortar and '몰탈상단' not in zh['t']:continue
heads=[e for e in texts if abs(e['y']-zh['y'])<span*.15 and e['x']<min(zh['x'],sh['x'])]
nhs=[e for e in heads if norm(e['t']) in ['교량받침','받침번호','NO.','NO']]
nhs=[h for h in nhs if any(re.fullmatch(r'\d+',e['t']) and abs(e['x']-h['x'])<span*.45 and 0<h['y']-e['y']<span*2 for e in texts)]
if not nhs:continue
nh=max(nhs,key=lambda e:e['x'])
phs=[e for e in heads if norm(e['t']) in ['구분','교각명','교각번호'] and e['x']<nh['x']]
if not phs:continue
ph=max(phs,key=lambda e:e['x'])
center=dict(x=(ph['x']+zh['x'])/2,y=zh['y'])
directions=[e for e in texts if drawing_direction(e['t']) and e['y']>zh['y'] and abs(e['x']-center['x'])<zh['x']-ph['x'] and e['y']-zh['y']<(zh['x']-ph['x'])]
direction_e=min(directions,key=lambda e:distance(e,center)) if directions else None
direction=drawing_direction(direction_e['t']) if direction_e else None
bottom=max([e['y'] for e in texts if norm(e['t'])==norm(zh['t']) and abs(e['x']-zh['x'])<span*.5 and e['y']<zh['y']]+[-float('inf')])
nums=sorted([e for e in texts if bottom<e['y']<zh['y'] and abs(e['x']-nh['x'])<span*.45 and re.fullmatch(r'\d+',e['t'])],key=lambda e:-e['y'])
if not nums:continue
pitch=nums[0]['y']-nums[1]['y'] if len(nums)>1 else zh['y']-nums[0]['y']
if pitch<=0 or pitch>span*2:continue
contiguous=[]
for e in nums:
if contiguous and abs(contiguous[-1]['y']-e['y']-pitch)>pitch*.15:break
contiguous.append(e)
groups=[]
for e in contiguous:
if not groups or int(e['t'])==1:groups.append([])
groups[-1].append(e)
for group in groups:
labels=[e for e in texts if re.fullmatch(r'[PA]\d+',norm(e['t'])) and abs(e['x']-ph['x'])<span*.45 and group[-1]['y']-pitch*.49<=e['y']<=group[0]['y']+pitch*.49]
if len(labels)!=1:
issues.append('ELEVATION 표: 받침 번호 묶음과 교각명 대응 미확정');continue
label=labels[0];p=norm(label['t'])
if p.startswith('A'):continue
for no in group:
values={};evidence=[label,nh,no,zh,sh]+mortar+([direction_e] if direction_e else [])
from .geometry import table_row_band
band=table_row_band(no,segments,span)
for field,h in [('z',zh),('seat',sh)]:
matches=[e for e in texts if (band[0]<e['y']<band[1] if band else abs(e['y']-no['y'])<pitch*.03) and abs(e['x']-h['x'])<span*.45 and re.fullmatch(r'(?:EL[. =]*)?'+NUMBER,e['t'],re.I)]
if len(matches)==1:
values[field]=float(re.search(NUMBER+r'$',matches[0]['t'])[0]);evidence.append(matches[0])
if values:out.append(dict(p=p,no=int(no['t']),direction=direction,evidence=evidence,**values))
else:issues.append(f'{p} 받침 {no["t"]}: 몰탈상단/교좌면 값 대응 미확정')
return out,issues
@@ -0,0 +1,64 @@
"""Standalone Python XLSX writer, explicitly requested by the user."""
from openpyxl import Workbook, load_workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.comments import Comment
from openpyxl.utils import get_column_letter
from .core import HEADERS
def put(ws,cell,value):
c=ws[cell];c.value=value
if isinstance(value,str):c.data_type='s' # CAD text must never execute as an Excel formula.
return c
def export_workbook(result,path):
wb=Workbook();wb.remove(wb.active)
font='맑은 고딕'
for name,headers in HEADERS.items():
s=wb.create_sheet(name);s.sheet_view.showGridLines=False;s.freeze_panes='D3'
put(s,'B1',result['name']+' '+name+' 입력데이터').font=Font(name=font,size=14,bold=True)
s.row_dimensions[1].height=30;s.row_dimensions[2].height=38
for j,title in enumerate(headers,2):
c=s.cell(2,j,title);c.font=Font(name=font,size=10,bold=True,color='FFFFFF');c.fill=PatternFill('solid',fgColor='203864');c.alignment=Alignment(horizontal='center',vertical='center',wrap_text=True)
s.column_dimensions[get_column_letter(j)].width=16
s.column_dimensions['B'].width=10;s.column_dimensions['C'].width=21
if name!='Pier':
s.column_dimensions['D'].width=21;s.column_dimensions['E'].width=21
if name=='Bearing':s.column_dimensions['G'].width=23
for cell,value in result['cells'][name].items():
c=put(s,cell,value);c.font=Font(name=font,size=10,color='C00000');c.alignment=Alignment(horizontal='center',vertical='center')
c.border=Border(*( [Side(style='thin',color='B7B7B7')]*4 ))
s.row_dimensions[c.row].height=24
if name=='Pier' and c.column in [4,5,6]:c.number_format='0.0000' if c.column==4 else '0.000'
elif name!='Pier' and c.column in [4,5]:c.number_format='0.000000' if name=='IP' else '0.0000'
elif name=='Bearing' and c.column==6:c.number_format='0.0000'
note_col=len(headers)+3
s.cell(2,note_col,'상태 / 추출 및 검토 근거').font=Font(name=font,bold=True,size=10)
s.column_dimensions[get_column_letter(note_col)].width=100
notes={}
for r in result['records']:
if r['sheet']!=name:continue
c=s[r['cell']]
if r['status'] not in ['추출','식별자','등록값']:
c.fill=PatternFill('solid',fgColor='FFF2CC')
body=[f"상태: {r['status']}",r['note'],*r['messages']]
for e in r['evidence']:
body.append(f"{result['sources'][e['doc']]['path']}\nhandle {e['h']:X}, ({e['x']:.4f}, {e['y']:.4f})\n{e['t']}")
if r['status']!='식별자':c.comment=Comment('\n'.join(filter(None,body)),'도면추출 프로그램')
if r['status'] not in ['추출','식별자']:
notes.setdefault(c.row,[]).append(f"{r['cell']} {r['status']}: "+' / '.join(r['messages']))
for row,messages in notes.items():
c=s.cell(row,note_col,'; '.join(messages));c.font=Font(name=font,size=10);c.alignment=Alignment(wrap_text=True,vertical='top')
s.row_dimensions[row].height=max(24,min(100,20*(len(c.value)//85+1)))
last=max([int(c[1:]) for c in result['cells'][name]]+[2])+3
for text in result.get('notes',[]):
put(s,f'B{last}',text).font=Font(name=font,size=10);last+=1
for d,src in result['sources'].items():
put(s,f'B{last}',f"출처 {d}: {src['path']}").font=Font(name=font,size=10);last+=1
wb.save(path)
# Check the actual serialized file, not only the in-memory workbook.
saved=load_workbook(path)
for sheet,cells in result['cells'].items():
for address,value in cells.items():
if saved[sheet][address].value!=value:
raise ValueError(f'엑셀 저장 대조 실패: {sheet}!{address}')
saved.close()
@@ -0,0 +1,80 @@
"""Folder-only default interface."""
import os
from pathlib import Path
import queue
import threading
import tkinter as tk
from tkinter import ttk,filedialog,messagebox
import sys
PACKAGE = Path(__file__).resolve().parents[1]
WORKSPACE = PACKAGE.parents[1]
sys.path.insert(0, str(PACKAGE / 'src'))
from bridge_drawing_extractor import extract_folder
def run_folder(folder, log):
result = extract_folder(folder,
converter_path=WORKSPACE / 'Code/giyang_extract/libredwg/dwgread.exe',
on_progress=log)
return result.output_dir, [(b.xlsx_path, b.data) for b in result.workbooks], result.files
class AutoApp:
def __init__(self,root):
self.root=root;self.busy=False;self.events=queue.Queue();self.last_output=None
root.title('도면 폴더 → 교각 입력 엑셀');root.geometry('960x640');root.minsize(760,520)
self.folder=tk.StringVar();self.status=tk.StringVar(value='도면이 들어 있는 폴더를 선택하세요.')
frame=ttk.Frame(root,padding=16);frame.pack(fill='both',expand=True);frame.columnconfigure(0,weight=1);frame.rowconfigure(5,weight=1)
ttk.Label(frame,text='도면 폴더에서 자동 추출',font=('맑은 고딕',16,'bold')).grid(row=0,column=0,columnspan=2,sticky='w',pady=(0,12))
ttk.Label(frame,text='종평면·받침배치·ELEVATION에서 추출합니다. 기존 엑셀 없이 IP 연결·500mm 단위 치수 산정.').grid(row=1,column=0,columnspan=2,sticky='w',pady=(0,12))
ttk.Entry(frame,textvariable=self.folder).grid(row=2,column=0,sticky='ew',padx=(0,8))
ttk.Button(frame,text='도면 폴더 선택',command=self.choose).grid(row=2,column=1)
buttons=ttk.Frame(frame);buttons.grid(row=3,column=0,columnspan=2,sticky='w',pady=12)
self.start_button=ttk.Button(buttons,text='자동 추출 시작',command=self.start);self.start_button.pack(side='left',padx=(0,8))
ttk.Button(buttons,text='결과 폴더 열기',command=self.open_output).pack(side='left')
self.progress=ttk.Progressbar(frame,mode='indeterminate');self.progress.grid(row=4,column=0,columnspan=2,sticky='ew',pady=(0,8))
self.text=tk.Text(frame,state='disabled',wrap='word',font=('맑은 고딕',10));self.text.grid(row=5,column=0,columnspan=2,sticky='nsew')
scroll=ttk.Scrollbar(frame,command=self.text.yview);scroll.grid(row=5,column=2,sticky='ns');self.text.configure(yscrollcommand=scroll.set)
ttk.Label(frame,textvariable=self.status,wraplength=870).grid(row=6,column=0,columnspan=2,sticky='w',pady=10)
ttk.Label(frame,text='결과: 선택 폴더의 _도면추출결과. 판독이 모호한 값은 공란·검토 사유로 표시합니다.',wraplength=870).grid(row=7,column=0,columnspan=2,sticky='w')
root.after(100,self.poll)
def choose(self):
folder=filedialog.askdirectory()
if folder:self.folder.set(folder)
def append(self,text):
self.text.configure(state='normal');self.text.insert('end',text+'\n');self.text.see('end');self.text.configure(state='disabled')
def start(self):
if self.busy:return
folder=self.folder.get()
if not folder or not Path(folder).is_dir():messagebox.showerror('폴더 선택','유효한 도면 폴더를 선택하세요.');return
self.busy=True;self.start_button.state(['disabled']);self.progress.start();self.status.set('도면을 읽고 있습니다…')
def worker():
try:self.events.put(('done',run_folder(folder,log=lambda s:self.events.put(('log',s)))))
except Exception as e:self.events.put(('error',str(e)))
threading.Thread(target=worker,daemon=True).start()
def poll(self):
try:
while True:
kind,value=self.events.get_nowait()
if kind=='log':self.append(value);continue
self.busy=False;self.start_button.state(['!disabled']);self.progress.stop()
if kind=='error':self.status.set('처리 실패 — 판독 메시지를 확인하세요.');self.append(value)
else:
folder,outputs,inventory=value;self.last_output=folder
issues=sum(r['value'] is None and r['status']!='대상외' for _,result in outputs for r in result['records'])
failed=sum(x['status']=='읽기 실패' for x in inventory)
self.status.set(f'엑셀 {len(outputs)}개 저장 · 검토 {issues}셀 · 파일 읽기 실패 {failed}개')
self.append('저장 위치: '+str(folder));self.append('CSV의 검토 사유와 파일판독내역.json을 확인하세요.')
except queue.Empty:pass
self.root.after(100,self.poll)
def open_output(self):
if self.last_output and self.last_output.exists():os.startfile(str(self.last_output))
if __name__ == '__main__':
root = tk.Tk()
app = AutoApp(root)
root.mainloop()
@@ -0,0 +1,244 @@
LibreDWG - free implementation of the DWG file format
LibreDWG is a free C library to read and write DWG files. This program is
part of the GNU project, released under the aegis of GNU. It is licensed
under the terms of the GNU General Public License version 3 (or at you option
any later version).
DWG is a file format created in the 70's for the emerging CAD applications.
Currently it is the native file format of AutoCAD, a proprietary CAD program
developed by AutoDesk.
LibreDWG is a fork from LibDWG due to its usage of Esperanto, which we
think is not the best strategy for a free software project which aims
to get lots of contributors. LibreDWG is written in English. At the
moment our decoder (i.e. reader) is done, it can read all DWG
versions, just some very advanced R2010+ objects fail to read and are
skipped over. The writer is good enough for everything but R2007.
R2010-R2018 writing leads to CRC errors still. Among the
example applications we wrote using LibreDWG is a reader, a writer, a
rewriter (i.e. saveas), an initial SVG and Postscript conversion, dxf
and json converters, dwggrep to search for text, and dwglayer to print
the list of layers. More are in the pipeline. Please contact us if you
want to help.
The strings API is UTF-8 (WTF8 really, Windows UCS-2 has no surrogate
pairs), and encodes to the internal 8-bit or UCS-2 strings, depending
on the DWG version. Older DWG's are encoded in ~30 codepages, which we
convert to unicode/UTF-8.
We also maintain a diff document for our internal fixed ODA-5.4.1 spec
at doc/ODA-5.4.2-libredwg-diff.pdf
For security contact file a ticket or email the package maintainers:
Reini Urban <rurban AT cpan.org>
The now inactive founders and previous maintainers were:
Felipe Corrêa da Silva Sanches <juca AT members.fsf.org>
Rodrigo Rodrigues da Silva <pitanga AT members.fsf.org>
Hardeep Singh Rai <hardeep.rai AT gmail DOT com>
If you are looking for general information about the project, check our website:
http://www.gnu.org/software/libredwg/
== Building and installing the library ==
You need the following dependencies:
- Basic development tools (a C99 compiler like gcc/clang, make, autoconf, automake
and libtool)
Optionally:
- libiconv: optional codepage conversion. without it's just a bit slower.
- libmimalloc: faster memory allocation, but usually not worth the hassle.
- pslib for dwg2ps: http://pslib.sourceforge.net/doc/pslib.html
- pcre2 with 8bit and 16bit libraries for dwggrep regexp support.
- gperf to update static hashes for several API's
Only needed when you added objects or changed the dwg.spec significantly
- cmake, ninja
- ODAFileConverter from https://www.opendesign.com/guestfiles/oda_file_converter
for doing cross-checks.
- xvfb-run from xvfb/xorg-x11-server-Xvfb for silencing ODAFileConverter popups.
- TeXinfo for building the docs
- SWIG 1.7+
- Python 2.7 or 3.x development headers (debian: python-dev; yum: python-devel)
and the libxml2 python bindings.
- Perl 5 for the perl bindings
- doxygen to generate the reference manual
- jq to check json validity, and as dwgfilter backend
- shellcheck to check shell scripts
- rpmlint to check the spec validity
- jinq with some svg11 relaxng to check SVG validity.
LaTeXML used to have a broken svg11-basic.rng. If so
the zip from http://yupotan.sppd.ne.jp/relax-ng/svg11/ is correct.
Needs to be installed into /usr/local/share/relaxng/svg11/
sudo unzip ~/Downloads/svg11-relaxng-20060604.zip -d /usr/local/share/relaxng/
On Ubuntu I needed:
apt install jing libjaxp1.3-java libbatik-java libavalon-framework-java
- mapbox/geojsonhint as geojson linter
npm install -g @mapbox/geojsonhint
- geojson-validation as 2nd geojson fallback linter
npm install -g geojson-validation
- shfmt to format shell scripts
- clang-format to format the C source code
- clang-tidy to check source code
- valgrind to find leaks and memory bugs
- timeout to help tests with large or hanging DWG's
- GNU parallel to speed up special tests (not the moreutils parallel!)
- picat to find unknown fields, a better prolog. http://picat-lang.org/
See INSTALL for generic instructions. Basically, you do:
$ sh ./autogen.sh (if you checked out the source code from git)
$ ./configure [--disable-bindings] [--disable-docs] [--disable-shared]
$ make
$ make check
$ sudo make install
This builds and installs various files in the "installation dirs":
$libdir ($exec_prefix/lib) -- library files (libredwg.*)
$includedir ($prefix/include) -- dwg.h dwg_api.h
$libdir/pkgconfig -- libredwg.pc
$infodir ($datarootdir/info) -- LibreDWG.info
Use "./configure --help" to see a full list of options you can use to
customize the installation dirs. Other options recognized by configure are:
--enable-release
Recommended for packagers and fuzzers. Disables all features
which will not be enabled in official tarball releases. Like skipping
unstable DWG features or objects, unknown DWG versions and objects.
--enable-trace
Enable runtime tracing via LIBREDWG_TRACE (default: no). When
enabled, the environment variable LIBREDWG_TRACE is consulted on
the first decode/encode attempt. Its value is an integer: 0 (no
output) through 9 (full verbosity). Most tools do support a
--verbosity|-v flag instead.
--with-dxf-precision=rfc
How many after-comma places in DXF doubles. 6 is the recommended
value (e.g. use rfc), max and the default is 16.
--with-geojson-precision=rfc
How many after-comma places in GeoJSON doubles. 6 is the
recommended value by RFC 7946, max and the default is 16.
--disable-write
Disable DWG write support (default: no).
Write support only works for earlier versions until r2004.
Rewriting most DWG's <= r2004 usually works fine.
--disable-bindings
Disable SWIG bindings for python and perl5.
--disable-dxf
Disables all in and out modules: DXF, DXFB, JSON, GeoJSON.
--disable-json
Disables the json in and out modules: JSON, GeoJSON.
--disable-python
Disable python bindings and tests.
Related: --enable-python=python3.6
to enforce a specific version.
--enable-debug
Activates support for unstable classes. For testing only.
--enable-gcov
Activates support for GNU gcov, test coverage reporting.
--with-perl-install=vendor
Where to install the perl bindings: vendor,arch,yes,no,site.
Default is perl-install=site
== Windows ==
See the following scripts how we build on Windows:
* .github/workflows/main.yml: via msys/autotools, or cmake. gcc, clang-cl or VS2019.
* build-aux/smoke.sh: cross-compilation recipes
* build-aux/msys2.bat: msys2 deps
* .appveyor.yml: windows smoker
We do support mingw, clang-cl and Visual Studio >= 2019 via cmake.
stdint.h and inttypes.h must be available.
Pre-compiled Windows binaries are here: https://github.com/LibreDWG/libredwg/releases
== Example usage of the library ==
The programs and examples directories contains some application code
that uses our lib:
* dwgread -- A DWG reader supporting various output formats, such as
JSON, DXF, DXFB, GeoJSON.
* dwgwrite -- A DWG writer supporting various input formats, such as DXF, DXFB, JSON.
* dxfwrite -- A DXF writer supporting various input formats, such as DWG, DXF, DXFB, JSON.
* dwg2dxf -- Converts a DWG to a DXF (ascii or binary, minimal or full), optionally
under a different version. About 90% coverage.
* dxf2dwg -- Converts a ascii or binary DXF to a DWG (r2000 only so far).
About 80% coverage.
* dwglayers -- Prints the list of layers in a DWG. Optionally with --extnames
* dwggrep -- Searches DWG files for a text string, via regular expressions.
* dwg2ps -- Opens a DWG file and outputs an PostScript file. This code
depends on the PSlib library. On Debian-based systems this is usually
available as a package named pslib-dev. This is very limited so far.
* dwgfilter -- Applies JSON filters via JQ. Either as structured grep,
or to mass change certain values.
* dwg2SVG -- Opens a DWG file and outputs an SVG (Scalable Vector Graphics)
file. SVG is a W3C standard for 2d vector graphics. You can open these
files on several free software tools. We recommend Inkscape, a free software
vector graphics editor. The dwg2SVG program will not handle 3d content
from DWG since SVG only supports 2-dimensional images. Handles only some
entities.
* dwgbmp -- Extracts the bmp thumbnail of a dwg file when it is available.
* dwgadd -- the easiest way to create DWG's (or DXF, JSON) from scratch or
add entities to an existing DWG. It accepts a very simple file with commands
to create entities or objects and set its properties.
* dwgrewrite -- Reads a DWG, writes it back under a different name and optionally
under a different version (default r2000), and re-reads it back for confirmation.
* load_dwg -- This is a skeleton code, reading the DWG and adding an entity.
Look there if you want to have a quickly glance on how to use the library.
* dwg2svg2 -- Another example how to use the DWG API, bypassing direct access to
the DWG struct.
* xmlsuite -- an extensive example library to use XML to compare against prepared
features is in the test/xmlsuite directory. It also features code to output entities
in XML, but does not use the python bindings.
Copyright (C) 2009, 2010, 2015, 2018-2026 Free Software Foundation, Inc.
This library is free software, licensed under the terms of the GNU
General Public License as published by the Free Software Foundation,
either version 3 of the License, or (at your option) any later version.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
@@ -0,0 +1,84 @@
#!/bin/sh
# options: --help, --version, --debug, -i
version() {
echo "dwgfilter 0.14"
exit
}
help() {
echo "dwgfilter [OPTIONS...] dwgfile"
echo ""
echo "Allow custom jq queries on a temporary JSON dump."
echo ""
echo "OPTIONS: --help,--version"
echo " --debug keep the tmp json"
echo " -i write back in-place, with an updating JQ query"
echo " ... all other options are passed to jq. See 'man jq'"
exit
}
opts=
# get last arg
for dwg; do true; done
for arg in "$@"
do
case $arg in
--help) help ;;
--version) version ;;
--debug) debug=1 ;;
"$dwg") if [ ! -f "$dwg" ]; then echo DWG "$dwg" not found; exit 1; fi ;;
-i) writemode=1 ;;
*) opts="$opts $arg" ;;
esac
done
if [ ! -f "$dwg" ]
then
echo Wrong input DWG "$dwg"
exit 1
fi
if [ -z "$opts" ]
then
echo Input JQ query arguments missing
exit 1
fi
prefix=""
exec_prefix="${prefix}"
jq=""
jq="${jq:-jq}"
json="$(mktemp "${TMPDIR:-/tmp}/dwgfilter.XXXXXX.json")" || exit 1
if [ -z "$debug" ]; then
trap 'rm -f "$json"' EXIT
trap 'exit 2' INT TERM
fi
selfpath="$(realpath "$0")"
if [ "$selfpath" = "${exec_prefix}/bin/dwgfilter" ]; then
dwgread="${exec_prefix}/bin/dwgread"
dwgwrite="${exec_prefix}/bin/dwgwrite"
else
dwgread="$(dirname "$selfpath")/dwgread"
dwgwrite="$(dirname "$selfpath")/dwgwrite"
fi
if [ -n "$debug" ]; then
dwgread="$dwgread -v3"
dwgwrite="$dwgwrite -v3"
fi
echo "$dwgread -O json -o $json $dwg"
$dwgread -O json -o "$json" "$dwg"
echo "$jq $opts $json"
$jq "$opts" "$json"
fail=$?
if [ $fail = 0 ] && [ -n "$writemode" ]; then
mv "$dwg" "$dwg.bak"
echo "$dwgwrite -o $dwg $json"
if $dwgwrite -o "$dwg" "$json"; then
:
else
mv "$dwg.bak" "$dwg"
fail=1
fi
fi
if [ -n "$debug" ]; then
echo "Keeping JSON dump: $json"
fi
exit $fail
@@ -0,0 +1,152 @@
/*****************************************************************************/
/* LibreDWG - free implementation of the DWG file format */
/* */
/* Copyright (C) 2018-2019 Free Software Foundation, Inc. */
/* */
/* This library is free software, licensed under the terms of the GNU */
/* General Public License as published by the Free Software Foundation, */
/* either version 3 of the License, or (at your option) any later version. */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*****************************************************************************/
/*
* bd.c: print double of bits
* written by Reini Urban
*
*/
#include "../src/config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dwg.h"
#include "../src/bits.h"
int
main (int argc, char *argv[])
{
char *input;
int hex = 0;
int size, bits;
unsigned long pos;
Bit_Chain dat = EMPTY_CHAIN (0);
if (argc < 2)
{
printf ("usage: examples/bd "
"001100000000000000000000000000011000000000000000100000001010010"
"00\n");
printf ("or examples/bd -x '8055 40f9 3284 d222 3e40 7436 e0d9 23fd'\n");
return 1;
}
if (argc > 2 && !strcmp (argv[1], "-x"))
{
hex = 1;
input = argv[2];
size = strlen (input);
}
else
{
input = argv[1];
bits = strlen (input);
size = (bits / 8);
}
dat.chain = malloc (size + 1);
dat.size = size;
dat.version = R_2004;
if (hex)
{
dat.size = bit_write_hexbits (&dat, input);
bits = dat.size / 8;
}
else
bit_write_bits1 (&dat, input);
pos = bit_position (&dat);
bit_set_position (&dat, 0);
// accept more types, like CMC, BS, BL, HANDLE and print all possible
// variants
if (pos == 64)
{
double d = bit_read_RD (&dat);
printf ("%.15f RD @%lu\n", d, bit_position (&dat));
}
else if (pos == 2 || pos > 64)
{
double d = bit_read_BD (&dat);
printf ("%.15f BD @%lu (%ld)\n", d, bit_position (&dat), pos);
if (pos == 2)
{
int i;
bit_set_position (&dat, 0);
i = bit_read_BB (&dat);
printf ("%d BB @%lu\n", i, bit_position (&dat));
if (i == 2)
printf ("%d BS/BL @%lu\n", 0, bit_position (&dat));
else if (i == 3)
printf ("%d BS @%lu\n", 256, bit_position (&dat));
}
}
else if (pos == 34)
{
long l = bit_read_BL (&dat);
printf ("%ld BL @%lu\n", l, bit_position (&dat));
}
else if (pos == 32)
{
long l = bit_read_RL (&dat);
printf ("%ld RL @%lu\n", l, bit_position (&dat));
}
else if (pos == 16)
{
int l = (int)bit_read_RS (&dat);
printf ("%d RS @%lu\n", l, bit_position (&dat));
}
else if (pos == 10)
{
long l = bit_read_BS (&dat);
Dwg_Handle h;
printf ("%ld BS @%lu\n", l, bit_position (&dat));
bit_set_position (&dat, 0);
if (!bit_read_H (&dat, &h) && h.size == 1)
printf (FORMAT_H " H @%lu (%ld)\n", ARGS_H (h), bit_position (&dat),
pos);
}
else if (pos == 8)
{
int l = (int)bit_read_RC (&dat);
Dwg_Handle h;
printf ("%d RC @%lu\n", l, bit_position (&dat));
bit_set_position (&dat, 0);
if (!bit_read_H (&dat, &h) && h.size == 1)
printf (FORMAT_H " H @%lu (%ld)\n", ARGS_H (h), bit_position (&dat),
pos);
}
else
{
Dwg_Handle h;
Dwg_Color c;
double d = bit_read_BD (&dat);
if (pos >= bit_position (&dat))
{
printf ("%.15f BD? @%lu (%ld)\n", d, bit_position (&dat), pos);
}
else
{ // divide into chunks of 34,32,18,16,10,8,2
printf ("? (%ld)\n", pos);
}
bit_set_position (&dat, 0);
if (!bit_read_H (&dat, &h))
printf (FORMAT_H " H? @%lu (%ld)\n", ARGS_H (h), bit_position (&dat),
pos);
bit_set_position (&dat, 0);
bit_read_CMC (&dat, &dat, &c);
if (c.index < 257)
printf ("%d 0x%06X 0x%x CMC? @%lu (%ld)\n", c.index, c.rgb, c.flag,
bit_position (&dat), pos);
}
free (dat.chain);
return 0;
}
@@ -0,0 +1,237 @@
/*****************************************************************************/
/* LibreDWG - free implementation of the DWG file format */
/* */
/* Copyright (C) 2018-2019 Free Software Foundation, Inc. */
/* */
/* This library is free software, licensed under the terms of the GNU */
/* General Public License as published by the Free Software Foundation, */
/* either version 3 of the License, or (at your option) any later version. */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*****************************************************************************/
/*
* bits.c: print all possible type/values of given bits
* written by Reini Urban
*/
#include "../src/config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "dwg.h"
#include "../src/bits.h"
static int maxoff = 0;
static int
decode (Bit_Chain *dat, int off, const int size)
{
int pos = 0;
BITCODE_BS bs = 0;
printf ("decode offset:%d, size:%d\n", off, size);
if (off >= size)
return 0;
bit_set_position (dat, off);
if (size - off >= 66)
{
double d = bit_read_BD (dat);
int p = (int)bit_position (dat);
if (d != bit_nan ())
{
printf ("%.15f BD @%d @%u.%u (%d)\n", d, p, p / 8, p % 8, size);
pos = p;
}
bit_set_position (dat, off);
}
if (size - off >= 64)
{
double d = bit_read_RD (dat);
int p = (int)bit_position (dat);
if (d != bit_nan ())
{
printf ("%.15f RD @%d @%u.%u (%d)\n", d, p, p / 8, p % 8, size);
pos = p;
}
bit_set_position (dat, off);
}
if (size - off >= 34)
{
BITCODE_BL l = (long)bit_read_BL (dat);
int p = (int)bit_position (dat);
if (p <= size - off)
{
printf ("%u BL @%d @%u.%u (%d)\n", l, p, p / 8, p % 8, size);
pos = p;
}
bit_set_position (dat, off);
}
if (size - off >= 32)
{
BITCODE_BL l = (long)bit_read_RL (dat);
pos = (int)bit_position (dat);
printf ("%u RL @%d @%u.%u (%d)\n", l, pos, pos / 8, pos % 8, size);
bit_set_position (dat, off);
}
if (size - off >= 16)
{
BITCODE_RS l = (int)bit_read_RS (dat);
pos = (int)bit_position (dat);
printf ("%d RS @%d (%d)\n", l, pos, size);
bit_set_position (dat, off);
}
if (size - off >= 10)
{
int p;
bs = bit_read_BS (dat);
p = (int)bit_position (dat);
if (p <= size - off)
{
printf ("%d BS @%d (%d)\n", bs, p, size);
pos = p;
}
bit_set_position (dat, off);
}
if (size - off >= 8)
{
int l = bit_read_RC (dat);
pos = (int)bit_position (dat);
printf ("%d RC @%d (%d)\n", l, pos, size);
bit_set_position (dat, off);
}
if (size - off >= 8)
{
Dwg_Handle h;
int err, p;
err = bit_read_H (dat, &h);
p = (int)bit_position (dat);
if (!err && h.size == 1 && p <= size - off)
{
printf ("%x.%d.%lX H @%d (%d)\n", h.code, h.size, h.value, p, size);
pos = p;
}
bit_set_position (dat, off);
}
if (size - off >= 4)
{
Dwg_Color c;
int p;
bit_read_CMC (dat, dat, &c);
p = (int)bit_position (dat);
if (c.index < 257 && p <= size - off)
{
printf ("%d 0x%06X 0x%x CMC @%d (%d)\n", c.index, c.rgb, c.flag, p,
size);
pos = p;
}
free (c.name);
free (c.book_name);
bit_set_position (dat, off);
}
if (size - off >= 2)
{
int i = bit_read_BB (dat);
pos = (int)bit_position (dat);
printf ("%d BB @%d (%d)\n", i, pos, size);
if (i == 2)
printf ("%d BS/BL @%d (%d)\n", 0, pos, size);
else if (i == 3)
printf ("%d BS @%d (%d)\n", 256, pos, size);
bit_set_position (dat, off);
}
// if BS is a valid length try TV also
if (bs > 0 && bs < size - off - pos)
{
int i;
char *s = bit_read_TV (dat);
int p = (int)bit_position (dat);
if (s)
{
for (i = 0; i < bs; i++)
if (!isprint (s[i]))
break;
if (i == bs)
{
printf ("%s TV @%d (%d)\n", s, pos, size);
pos = p;
}
free (s);
}
}
while (pos < size)
{
if (pos <= off)
pos++;
if (maxoff < pos)
maxoff = pos;
// printf ("offset %d\n", pos);
return decode (dat, off + 1, size);
}
return 0;
}
int
main (int argc, char *argv[])
{
int hex = 0;
int print_bits = 0;
int i;
int pos;
Bit_Chain dat = EMPTY_CHAIN (0);
if (argc < 2)
{
printf ("usage: examples/bits "
"001100000000000000000000000000011000000000000000100000001010010"
"00\n");
printf (
"or examples/bits -x '8055 40f9 3284 d222 3e40 7436 e0d9 23fd'\n");
return 1;
}
i = 1;
if (argc > 2 && !strcmp (argv[i], "-x"))
{
hex = 1;
i++;
}
if (argc > 2 && !strcmp (argv[i], "-b"))
{
print_bits = 1;
i++;
}
// dat.chain = malloc (size + 1);
// dat.size = size;
dat.version = R_2004;
do
{
char *input = argv[i];
int bits = strlen (input);
if (hex)
dat.size += bits;
else
dat.size += bits / 8;
dat.chain = realloc (dat.chain, dat.size + 1);
if (hex)
{
int size = bit_write_hexbits (&dat, input);
dat.size -= dat.size - size;
}
else
bit_write_bits1 (&dat, input);
i++;
}
while (i < argc);
pos = (int)bit_position (&dat);
if (print_bits)
bit_print_bits (&dat.chain[0], pos);
// accept all types, like CMC, BS, BL, HANDLE and print all possible variants
pos = decode (&dat, 0, (int)pos);
free (dat.chain);
return 0;
}
@@ -0,0 +1,531 @@
/*****************************************************************************/
/* LibreDWG - free implementation of the DWG file format */
/* */
/* Copyright (C) 2013-2020,2023 Free Software Foundation, Inc. */
/* */
/* This library is free software, licensed under the terms of the GNU */
/* General Public License as published by the Free Software Foundation, */
/* either version 3 of the License, or (at your option) any later version. */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*****************************************************************************/
/*
* dwg2svg2.c: convert a DWG to SVG via the API.
if there are paperspace entities, only output them. else all modelspace
entities.
* written by Gaganjyot Singh
* modified by Reini Urban
*/
#include "../src/config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include <getopt.h>
#ifdef ENABLE_MIMALLOC
# include <mimalloc-override.h>
#endif
#include <dwg.h>
#include <dwg_api.h>
#include "geom.h"
static int opts = 0;
static dwg_data g_dwg;
static double model_xmin, model_ymin;
static double page_width, page_height, scale;
static int
usage (void)
{
printf ("\nUsage: dwg2svg2 [-v[0-9]] DWGFILE\n");
return 1;
}
static int
opt_version (void)
{
printf ("dwg2svg2 %s\n", PACKAGE_VERSION);
return 0;
}
static int
help (void)
{
printf ("\nUsage: dwg2svg2 [OPTION]... DWGFILE >file.svg\n");
printf ("Example to use the DWG api\n"
"\n");
#ifdef HAVE_GETOPT_LONG
printf (" -v[0-9], --verbose [0-9] verbosity\n");
printf (" --help display this help and exit\n");
printf (" --version output version information and exit\n"
"\n");
#else
printf (" -v[0-9] verbosity\n");
printf (" -h display this help and exit\n");
printf (" -i output version information and exit\n"
"\n");
#endif
printf ("GNU LibreDWG online manual: "
"<https://www.gnu.org/software/libredwg/>\n");
return 0;
}
#define log_if_error(msg) \
if (error) \
{ \
fprintf (stderr, "ERROR: %s\n", msg); \
exit (1); \
}
#define log_error(msg) \
{ \
fprintf (stderr, "ERROR: %s\n", msg); \
exit (1); \
}
#define dynget(obj, name, field, var) \
if (!dwg_dynapi_entity_value (obj, "" name, "" field, var, NULL)) \
{ \
fprintf (stderr, "ERROR: %s.%s\n", name, field); \
exit (1); \
}
#define dynget_utf8(obj, name, field, var) \
if (!dwg_dynapi_entity_utf8text (obj, "" name, "" field, var, &isnew, \
NULL)) \
{ \
fprintf (stderr, "ERROR: %s.%s\n", name, field); \
exit (1); \
}
static double
transform_X (double x)
{
return x - model_xmin;
}
static double
transform_Y (double y)
{
return page_height - (y - model_ymin);
}
static void output_SVG (dwg_data *dwg);
static int
test_SVG (char *filename)
{
int error;
memset (&g_dwg, 0, sizeof (dwg_data));
g_dwg.opts = opts;
error = dwg_read_file (filename, &g_dwg);
if (error < DWG_ERR_CRITICAL)
output_SVG (&g_dwg);
dwg_free (&g_dwg);
/* This value is the return value for `main',
so clamp it to either 0 or 1. */
return error < DWG_ERR_CRITICAL ? 0 : 1;
}
static void
output_TEXT (dwg_object *obj)
{
int error, index;
dwg_point_2d ins_pt;
Dwg_Entity_TEXT *text;
char *text_value;
double fontsize;
const Dwg_Version_Type dwg_version = obj->parent->header.version;
int isnew = 0;
index = dwg_object_get_index (obj, &error);
log_if_error ("object_get_index");
text = dwg_object_to_TEXT (obj);
if (!text)
log_error ("dwg_object_to_TEXT");
dynget_utf8 (text, "TEXT", "text_value", &text_value);
dynget (text, "TEXT", "ins_pt", &ins_pt);
dynget (text, "TEXT", "height", &fontsize);
printf ("\t<text id=\"dwg-object-%d\" x=\"%f\" y=\"%f\" "
"font-family=\"Verdana\" font-size=\"%f\" fill=\"blue\">%s</text>\n",
index, transform_X (ins_pt.x), transform_Y (ins_pt.y), fontsize,
text_value);
if (text_value && isnew)
free (text_value);
}
static void
output_LINE (dwg_object *obj)
{
int error, index;
Dwg_Entity_LINE *line;
dwg_point_3d start, end;
index = dwg_object_get_index (obj, &error);
log_if_error ("object_get_index");
line = dwg_object_to_LINE (obj);
if (!line)
log_error ("dwg_object_to_LINE");
if (!dwg_get_LINE (line, "start", &start))
log_error ("LINE.start");
if (!dwg_get_LINE (line, "end", &end))
log_error ("LINE.end");
printf ("\t<path id=\"dwg-object-%d\" d=\"M %f,%f %f,%f\" "
"style=\"fill:none;stroke:blue;stroke-width:0.1px\" />\n",
index, transform_X (start.x), transform_Y (start.y),
transform_X (end.x), transform_Y (end.y));
}
static void
output_CIRCLE (dwg_object *obj)
{
Dwg_Entity_CIRCLE *circle;
int error, index;
double radius;
dwg_point_3d center;
index = dwg_object_get_index (obj, &error);
log_if_error ("object_get_index");
circle = dwg_object_to_CIRCLE (obj);
if (!circle)
log_error ("dwg_object_to_CIRCLE");
if (!dwg_get_CIRCLE (circle, "center", &center))
log_error ("CIRCLE.center");
if (!dwg_get_CIRCLE (circle, "radius", &radius))
log_error ("CIRCLE.radius");
printf ("\t<circle id=\"dwg-object-%d\" cx=\"%f\" cy=\"%f\" r=\"%f\" "
"fill=\"none\" stroke=\"blue\" stroke-width=\"0.1px\" />\n",
index, transform_X (center.x), transform_Y (center.y), radius);
}
static void
output_ARC (dwg_object *obj)
{
Dwg_Entity_ARC *arc;
int error, index;
double radius, start_angle, end_angle;
dwg_point_3d center;
double x_start, y_start, x_end, y_end;
int large_arc;
index = dwg_object_get_index (obj, &error);
log_if_error ("object_get_index");
arc = dwg_object_to_ARC (obj);
if (!arc)
log_error ("dwg_object_to_ARC");
dynget (arc, "ARC", "radius", &radius);
dynget (arc, "ARC", "center", &center);
dynget (arc, "ARC", "start_angle", &start_angle);
dynget (arc, "ARC", "end_angle", &end_angle);
x_start = center.x + radius * cos (start_angle);
y_start = center.y + radius * sin (start_angle);
x_end = center.x + radius * cos (end_angle);
y_end = center.y + radius * sin (end_angle);
// Assuming clockwise arcs.
large_arc = (end_angle - start_angle < M_PI) ? 0 : 1;
printf ("\t<path id=\"dwg-object-%d\" d=\"M %f,%f A %f,%f 0 %d 0 %f,%f\" "
"fill=\"none\" stroke=\"blue\" stroke-width=\"%f\" />\n",
index, transform_X (x_start), transform_Y (y_start), radius, radius,
large_arc, transform_X (x_end), transform_Y (y_end), 0.1);
}
static void
output_INSERT (dwg_object *obj)
{
int index, error;
BITCODE_RL abs_ref;
double rotation;
dwg_ent_insert *insert;
dwg_point_3d ins_pt, _scale;
dwg_handle *obj_handle, *ins_handle;
Dwg_Data *dwg;
insert = dwg_object_to_INSERT (obj);
if (!insert)
log_error ("dwg_object_to_INSERT");
dwg = obj->parent;
index = dwg_object_get_index (obj, &error);
log_if_error ("object_get_index");
dynget (insert, "INSERT", "rotation", &rotation);
dynget (insert, "INSERT", "ins_pt", &ins_pt);
dynget (insert, "INSERT", "scale", &_scale);
obj_handle = dwg_object_get_handle (obj, &error);
log_if_error ("get_handle");
if (!insert->block_header)
log_error ("insert->block_header");
abs_ref = insert->block_header->absolute_ref;
if (insert->block_header->handleref.code == 5 || dwg->header.version < R_13)
{
printf ("\t<use id=\"dwg-object-%d\" transform=\"translate(%f %f) "
"rotate(%f) scale(%f %f)\" xlink:href=\"#symbol-%X\" /><!-- "
"block_header->handleref: " FORMAT_H " -->\n",
index, transform_X (ins_pt.x), transform_Y (ins_pt.y),
(180.0 / M_PI) * rotation, _scale.x, _scale.y, abs_ref,
ARGS_H (*obj_handle));
}
else
{
printf ("\n\n<!-- WRONG INSERT(" FORMAT_H ") -->\n",
ARGS_H (*obj_handle));
}
}
static int
output_object (dwg_object *obj)
{
int i = 0;
if (!obj)
{
fprintf (stderr, "object is NULL\n");
return 0;
}
if (dwg_object_get_fixedtype (obj) == DWG_TYPE_INSERT)
{
i++;
output_INSERT (obj);
}
else if (dwg_object_get_fixedtype (obj) == DWG_TYPE_LINE)
{
i++;
output_LINE (obj);
}
else if (dwg_object_get_fixedtype (obj) == DWG_TYPE_CIRCLE)
{
i++;
output_CIRCLE (obj);
}
else if (dwg_object_get_fixedtype (obj) == DWG_TYPE_TEXT)
{
i++;
output_TEXT (obj);
}
else if (dwg_object_get_fixedtype (obj) == DWG_TYPE_ARC)
{
i++;
output_ARC (obj);
}
return i;
}
static int
output_BLOCK_HEADER (dwg_object_ref *ref)
{
dwg_object *hdr, *obj;
dwg_obj_block_header *_hdr;
int error;
BITCODE_RL abs_ref;
char *name;
int i = 0;
if (!ref)
{
fprintf (stderr,
"Empty BLOCK."
" Could not output an SVG symbol for this BLOCK_HEADER\n");
return 0;
}
hdr = dwg_ref_get_object (ref, &error);
if (!hdr || error)
{
abs_ref = dwg_ref_get_absref (ref, &error);
fprintf (stderr, "Failed to resolve BLOCK handle %X.\n",
(unsigned)abs_ref);
return 0;
}
abs_ref = dwg_ref_get_absref (ref, &error);
_hdr = dwg_object_to_BLOCK_HEADER (hdr);
if (_hdr)
{
i++;
dynget (_hdr, "BLOCK_HEADER", "name", &name);
// name = dwg_obj_block_header_get_name (_hdr, &error);
printf ("\t<g id=\"symbol-%X\" >\n\t\t<!-- %s -->\n",
abs_ref ? abs_ref : 0, name ? name : "");
if (name != NULL && name != _hdr->name
&& hdr->parent->header.version >= R_2007)
free (name);
}
else
printf ("\t<g id=\"symbol-%X\" >\n\t\t<!-- ? -->\n",
abs_ref ? abs_ref : 0);
obj = get_first_owned_entity (hdr);
while (obj)
{
i += output_object (obj);
obj = get_next_owned_entity (hdr, obj);
}
printf ("\t</g>\n");
return i;
}
static void
output_SVG (dwg_data *dwg)
{
unsigned int i, num_hdr_objs;
int error;
dwg_obj_block_control *_ctrl;
dwg_object_ref *hdr;
dwg_object_ref **hdr_refs;
dwg_object_ref *ms = dwg_model_space_ref (dwg);
dwg_object_ref *ps = dwg_paper_space_ref (dwg);
double dx = dwg_model_x_max (dwg) - dwg_model_x_min (dwg);
double dy = dwg_model_y_max (dwg) - dwg_model_y_min (dwg);
double pdx = dwg->header_vars.PLIMMAX.x - dwg->header_vars.PLIMMIN.x;
double pdy = dwg->header_vars.PLIMMAX.y - dwg->header_vars.PLIMMIN.y;
double scale_x = dx / (pdx == 0.0 ? 1.0 : pdx);
double scale_y = dy / (pdy == 0.0 ? 1.0 : pdy);
scale = 25.4 / 72.0; // pt:mm TODO
model_xmin = dwg_model_x_min (dwg);
model_ymin = dwg_model_y_min (dwg);
page_width = dx;
page_height = dy;
scale *= (scale_x > scale_y ? scale_x : scale_y);
_ctrl = dwg_block_control (dwg);
hdr_refs = dwg_obj_block_control_get_block_headers (_ctrl, &error);
log_if_error ("block_control_get_block_headers");
num_hdr_objs = dwg_obj_block_control_get_num_entries (_ctrl, &error);
log_if_error ("block_control_get_num_entries");
printf ("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n"
"<svg\n"
" xmlns:svg=\"http://www.w3.org/2000/svg\"\n"
" xmlns=\"http://www.w3.org/2000/svg\"\n"
" xmlns:xlink=\"http://www.w3.org/1999/xlink\"\n"
" version=\"1.1\"\n"
" width=\"%f\"\n"
" height=\"%f\"\n"
">\n",
page_width, page_height);
printf ("\t<defs>\n");
for (i = 0; i < num_hdr_objs; i++)
{
hdr = hdr_refs[i];
if (hdr == ms || hdr == ps)
continue;
output_BLOCK_HEADER (hdr_refs[i]);
}
printf ("\t</defs>\n");
if (ps)
i = output_BLOCK_HEADER (ps);
if (!ps || !i)
output_BLOCK_HEADER (ms);
free (hdr_refs);
printf ("</svg>\n");
}
int
main (int argc, char *argv[])
{
int i = 1;
int c;
#ifdef HAVE_GETOPT_LONG
int option_index = 0;
static struct option long_options[]
= { { "verbose", 1, &opts, 1 }, // optional
{ "help", 0, 0, 0 },
{ "version", 0, 0, 0 },
{ NULL, 0, NULL, 0 } };
#endif
if (argc < 2)
return usage ();
while
#ifdef HAVE_GETOPT_LONG
((c = getopt_long (argc, argv, ":v::h", long_options, &option_index))
!= -1)
#else
((c = getopt (argc, argv, ":v::hi")) != -1)
#endif
{
if (c == -1)
break;
switch (c)
{
case ':': // missing arg
if (optarg && !strcmp (optarg, "v"))
{
opts = 1;
break;
}
fprintf (stderr, "%s: option '-%c' requires an argument\n", argv[0],
optopt);
break;
#ifdef HAVE_GETOPT_LONG
case 0:
/* This option sets a flag */
if (!strcmp (long_options[option_index].name, "verbose"))
{
if (opts < 0 || opts > 9)
return usage ();
# if defined(USE_TRACING) && defined(HAVE_SETENV)
{
char v[2];
*v = opts + '0';
*(v + 1) = 0;
setenv ("LIBREDWG_TRACE", v, 1);
}
# endif
break;
}
if (!strcmp (long_options[option_index].name, "version"))
return opt_version ();
if (!strcmp (long_options[option_index].name, "help"))
return help ();
break;
#else
case 'i':
return opt_version ();
#endif
case 'v': // support -v3 and -v
i = (optind > 0 && optind < argc) ? optind - 1 : 1;
if (!memcmp (argv[i], "-v", 2))
{
opts = argv[i][2] ? argv[i][2] - '0' : 1;
}
if (opts < 0 || opts > 9)
return usage ();
#if defined(USE_TRACING) && defined(HAVE_SETENV)
{
char v[2];
*v = opts + '0';
*(v + 1) = 0;
setenv ("LIBREDWG_TRACE", v, 1);
}
#endif
break;
case 'h':
return help ();
case '?':
fprintf (stderr, "%s: invalid option '-%c' ignored\n", argv[0],
optopt);
break;
default:
return usage ();
}
}
i = optind;
if (i >= argc)
return usage ();
return test_SVG (argv[i]);
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,834 @@
/*****************************************************************************/
/* LibreDWG - free implementation of the DWG file format */
/* */
/* Copyright (C) 2020 Free Software Foundation, Inc. */
/* */
/* This library is free software, licensed under the terms of the GNU */
/* General Public License as published by the Free Software Foundation, */
/* either version 3 of the License, or (at your option) any later version. */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*****************************************************************************/
/*
* dwgfuzz.c: afl++/honggfuzz fuzzing for all in- and exporters. Just not the
separate ones.
*
* Also usable like:
../configure --disable-shared --disable-bindings CC=hfuzz-clang CFLAGS='-O2
-g -fsanitize=address,undefined -fno-omit-frame-pointer -I/usr/local/include'
&& make -C src && make -C examples dwgfuzz honggfuzz -i ../.fuzz-in-dxf --
examples/dwgfuzz -indxf ___FILE___
* Also useful for debugging the fuzzers.
AFL_DONT_OPTIMIZE=1 AFL_LLVM_INSTRIM=1 AFL_USE_ASAN=1 make -C examples
dwgfuzz V=1 AFL_DEBUG=15 AFL_DEBUG_CHILD_OUTPUT=1 gdb --args afl-fuzz -m none
-i ../.fuzz-in-dxf/ -o .fuzz-out/ examples/dwgfuzz (gdb) set follow-fork-mode
child
* written by Reini Urban
*/
#include "../src/config.h"
#ifdef HAVE_SSCANF_S
# define __STDC_WANT_LIB_EXT1__ 1
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <getopt.h>
#include <dwg.h>
#include <dwg_api.h>
#include "common.h"
#include "decode.h"
#include "encode.h"
#include "bits.h"
#ifndef DISABLE_DXF
# include "out_dxf.h"
# ifndef DISABLE_JSON
# include "in_json.h"
# include "out_json.h"
# endif
# include "in_dxf.h"
#endif
#define FUZZ_INMEM 0
#define FUZZ_STDIN 1
#define FUZZ_FILE 2
// defined by ./make-afl-clang-fast.sh for INMEM. defaults to FILE for
// debugging
#ifndef FUZZ_MODE
# define FUZZ_MODE FUZZ_INMEM
// #define FUZZ_MODE FUZZ_STDIN
// #define FUZZ_MODE FUZZ_FILE
#endif
int dwg_fuzz_dat (Dwg_Data **restrict dwgp, Bit_Chain *restrict dat);
static int
version (void)
{
#if FUZZ_MODE == FUZZ_INMEM
printf ("dwgfuzz %s INMEM\n", PACKAGE_VERSION);
#elif FUZZ_MODE == FUZZ_FILE
printf ("dwgfuzz %s FILE\n", PACKAGE_VERSION);
#elif FUZZ_MODE == FUZZ_STDIN
printf ("dwgfuzz %s STDIN\n", PACKAGE_VERSION);
#else
error unknown FUZZ_MODE
#endif
#ifndef __AFL_COMPILER
printf ("not instrumented\n");
#else
# ifdef __AFL_FUZZ_TESTCASE_BUF
printf ("shared-memory instrumented\n");
# else
printf ("instrumented\n");
# endif
#endif
return 0;
}
#ifndef __AFL_COMPILER
# define __AFL_FUZZ_INIT()
# if FUZZ_MODE == FUZZ_INMEM
unsigned char *__AFL_FUZZ_TESTCASE_BUF;
unsigned long __AFL_FUZZ_TESTCASE_LEN;
# define __AFL_INIT() \
fp = fopen (argv[2], "rb"); \
if (!fp) \
return 0; \
__AFL_FUZZ_TESTCASE_LEN = dat.size; \
dat_read_file (&dat, fp, argv[2]); \
__AFL_FUZZ_TESTCASE_BUF = dat.chain;
# else
# define __AFL_INIT()
# endif
# define __AFL_FUZZ_TESTCASE_LEN dat.size
# define __AFL_LOOP(i) 1
#endif
__AFL_FUZZ_INIT ();
static int
help (void)
{
#if FUZZ_MODE == FUZZ_FILE
printf ("\nUsage: dwgfuzz MODE @@\n");
#elif !defined __AFL_COMPILER
printf ("\nUsage: dwgfuzz MODE ../.fuzz-in/INFILE\n");
#else
printf ("\nUsage: dwgfuzz MODE\n");
#endif
printf ("afl++ clang-fast shared-memory backend, using many importers and "
"exporters.\n"
"\n");
printf ("MODE:\n");
#ifdef USE_WRITE
# ifndef DISABLE_DXF
printf (" -indxf: import from DXF, export as DWG\n");
# endif
# ifndef DISABLE_JSON
printf (" -injson: import from JSON, export as DWG\n");
# endif
printf (" -rw: import from DWG, export as DWG, re-import from "
"this DWG (rewrite)\n");
printf (" -add: import from special add file, export as DWG and DXF, "
"re-import from this\n");
#endif
printf (" -dwg: import from DWG only\n");
#ifndef DISABLE_DXF
printf (" -dxf: import from DWG, export as DXF\n");
printf (" -dxfb: import from DWG, export as binary DXF\n");
#endif
#ifndef DISABLE_JSON
printf (" -json: import from DWG, export as JSON\n");
printf (" -geojson: import from DWG, export as GeoJSON\n");
#endif
printf ("\n"
" --version display the version and exit\n");
printf (" --help display this help and exit\n");
return 0;
}
// for LLVMFuzzerTestOneInput see llvmfuzz.c
int
main (int argc, char *argv[])
{
int i = 0;
Dwg_Data dwg;
Bit_Chain dat = { 0 };
Bit_Chain out_dat = { 0 };
FILE *fp;
struct stat attrib;
enum
{
INVALID,
DWG,
#if defined(USE_WRITE) && !defined(DISABLE_DXF)
INDXF,
#endif
#if defined(USE_WRITE) && !defined(DISABLE_JSON)
INJSON,
#endif
#if defined(USE_WRITE)
RW,
ADD,
#endif
#if !defined(DISABLE_DXF)
DXF,
DXFB,
#endif
#if !defined(DISABLE_JSON)
JSON,
GEOJSON,
#endif
} mode
= INVALID;
if (argc <= 1 || !*argv[1])
return 1;
if (strEQc (argv[1], "--version"))
return version ();
if (strEQc (argv[1], "--help"))
return help ();
__AFL_INIT ();
if (strEQc (argv[1], "-dwg"))
mode = DWG;
#ifdef USE_WRITE
# ifndef DISABLE_DXF
else if (strEQc (argv[1], "-indxf"))
mode = INDXF;
# endif
# ifndef DISABLE_JSON
else if (strEQc (argv[1], "-injson"))
mode = INJSON;
# endif
else if (strEQc (argv[1], "-rw"))
mode = RW;
else if (strEQc (argv[1], "-add"))
mode = ADD;
#endif /* USE_WRITE */
#ifndef DISABLE_DXF
else if (strEQc (argv[1], "-dxf"))
mode = DXF;
else if (strEQc (argv[1], "-dxfb"))
mode = DXFB;
#endif
#ifndef DISABLE_JSON
else if (strEQc (argv[1], "-json"))
mode = JSON;
else if (strEQc (argv[1], "-geojson"))
mode = GEOJSON;
#endif
else
return 1;
if (mode == INVALID)
return 1;
#if FUZZ_MODE == FUZZ_FILE
if (argc <= 2 || !*argv[2])
return 1;
#endif
#if FUZZ_MODE == FUZZ_INMEM && !defined(__AFL_COMPILER)
if (argc <= 2 || !*argv[2])
return 1;
#endif
memset (&dwg, 0, sizeof (dwg));
dat.chain = NULL;
// dat.opts = 3;;
#if FUZZ_MODE == FUZZ_INMEM
dat.chain = __AFL_FUZZ_TESTCASE_BUF;
#endif
while (__AFL_LOOP (10000))
{ // llvm_mode persistent, non-forking mode
i++;
#ifndef __AFL_COMPILER
# ifdef USE_WRITE
if (mode == ADD && i > 1)
break;
# endif
if (i > 3)
break;
#endif
#if FUZZ_MODE == FUZZ_INMEM
// fastest mode via shared mem
if (!dat.chain)
dat.chain = __AFL_FUZZ_TESTCASE_BUF;
dat.size = __AFL_FUZZ_TESTCASE_LEN;
printf ("Fuzzing from shared memory (%" PRIuSIZE ")\n", dat.size);
#elif FUZZ_MODE == FUZZ_STDIN
// still 10x faster than the old file-forking fuzzer.
dat.size = 0;
// dat.chain = NULL;
dat_read_stream (&dat, stdin);
printf ("Fuzzing from stdin (%" PRIuSIZE ")\n", dat.size);
#elif FUZZ_MODE == FUZZ_FILE
dat.size = 0;
fp = fopen (argv[2], "rb");
if (!fp)
return 0;
dat_read_file (&dat, fp, argv[2]);
fclose (fp);
printf ("Fuzzing from file (%" PRIuSIZE ")\n", dat.size);
#else
# error Missing FUZZ_MODE
#endif
if (dat.size == 0)
exit (1);
if (dat.size < 100)
continue; // useful minimum input length
memset (&out_dat, 0, sizeof (out_dat));
bit_chain_set_version (&out_dat, &dat);
#ifdef _WIN32
out_dat.fh = fopen ("NUL", "w");
#else
out_dat.fh = fopen ("/dev/null", "w");
#endif
switch (mode)
{
case DWG:
if (dwg_decode (&dat, &dwg) >= DWG_ERR_CRITICAL)
exit (0);
break;
#if defined(USE_WRITE) && !defined(DISABLE_DXF)
case INDXF:
if (dwg_read_dxf (&dat, &dwg) < DWG_ERR_CRITICAL)
{
if (dat.version == R_2007)
out_dat.version = R_2010;
if (dwg_encode (&dwg, &out_dat) >= DWG_ERR_CRITICAL)
exit (0);
free (out_dat.chain);
out_dat.chain = NULL;
}
break;
#endif
#if defined(USE_WRITE)
case RW:
if (dwg_decode (&dat, &dwg) < DWG_ERR_CRITICAL)
{
if (dat.version == R_2007)
out_dat.version = R_2010;
if (dwg_encode (&dwg, &out_dat) >= DWG_ERR_CRITICAL)
exit (0);
if (dwg_decode (&out_dat, &dwg) >= DWG_ERR_CRITICAL)
exit (0);
free (out_dat.chain);
out_dat.chain = NULL;
}
break;
case ADD:
{
Dwg_Data *dwgp = &dwg;
if (dwg_fuzz_dat (&dwgp, &dat) == 0)
{
int error;
out_dat.byte = 0;
out_dat.bit = 0;
out_dat.from_version = dwg.header.from_version;
out_dat.version = dwg.header.version;
out_dat.opts = dwg.opts;
error = dwg_encode (&dwg, &out_dat); // dwg -> out_dat
if (error >= DWG_ERR_CRITICAL)
exit (0);
out_dat.byte = 0;
out_dat.bit = 0;
memset (&dwg, 0, sizeof (Dwg_Data));
error = dwg_decode (&out_dat, &dwg); // out_dat -> dwg
if (error >= DWG_ERR_CRITICAL)
exit (0);
}
}
break;
#endif
#if defined(USE_WRITE) && !defined(DISABLE_JSON)
case INJSON:
if (dwg_read_json (&dat, &dwg) < DWG_ERR_CRITICAL)
{
if (dat.version == R_2007)
out_dat.version = R_2010;
if (dwg_encode (&dwg, &out_dat) >= DWG_ERR_CRITICAL)
exit (0);
}
break;
#endif
#ifndef DISABLE_DXF
case DXF:
if (dwg_decode (&dat, &dwg) < DWG_ERR_CRITICAL)
{
if (dwg_write_dxf (&out_dat, &dwg) >= DWG_ERR_CRITICAL)
exit (0);
}
break;
case DXFB:
if (dwg_decode (&dat, &dwg) < DWG_ERR_CRITICAL)
{
if (dwg_write_dxfb (&out_dat, &dwg) >= DWG_ERR_CRITICAL)
exit (0);
}
break;
#endif
#ifndef DISABLE_JSON
case JSON:
if (dwg_decode (&dat, &dwg) < DWG_ERR_CRITICAL)
{
if (dwg_write_json (&out_dat, &dwg) >= DWG_ERR_CRITICAL)
exit (0);
}
break;
case GEOJSON:
if (dwg_decode (&dat, &dwg) < DWG_ERR_CRITICAL)
{
if (dwg_write_geojson (&out_dat, &dwg) >= DWG_ERR_CRITICAL)
exit (0);
}
break;
#endif
case INVALID:
default:
exit (1);
}
free (out_dat.chain);
fclose (out_dat.fh);
}
dwg_free (&dwg);
return 0;
}
#if defined(USE_WRITE)
static dwg_point_2d *
scan_pts2d (unsigned num_pts, char **pp)
{
char *p = *pp;
dwg_point_2d *pts;
p = strchr (p, '(');
if (!p)
return NULL;
p++;
if (num_pts > 5000)
exit (0);
pts = calloc (num_pts, 16);
if (!pts)
exit (0);
for (unsigned i = 0; i < num_pts; i++)
{
if (sscanf (p, "(%lf %lf)", &pts[i].x, &pts[i].y))
{
p = strchr (p, ')');
if (!p)
break;
p++;
}
else
{
*pp = p;
p = NULL;
break;
}
}
if (p)
{
*pp = p;
return pts;
}
else
{
free (pts);
return NULL;
}
}
static dwg_point_3d *
scan_pts3d (unsigned num_pts, char **pp)
{
char *p = *pp;
dwg_point_3d *pts;
p = strchr (p, '(');
if (!p)
return NULL;
p++;
if (num_pts > 5000)
exit (0);
pts = calloc (num_pts, 24);
if (!pts)
exit (0);
for (unsigned i = 0; i < num_pts; i++)
{
if (sscanf (p, "(%lf %lf %lf)", &pts[i].x, &pts[i].y, &pts[i].z))
{
p = strchr (p, ')');
if (!p)
break;
p++;
}
else
{
*pp = p;
p = NULL;
break;
}
}
if (p && num_pts)
{
*pp = p;
return pts;
}
else
{
free (pts);
return NULL;
}
}
static char *
next_line (char *restrict p, const char *restrict end)
{
while (p < end && *p != '\n')
p++;
if (p < end)
p++;
return p;
}
int
dwg_fuzz_dat (Dwg_Data **restrict dwgp, Bit_Chain *restrict dat)
{
Dwg_Data *dwg;
Dwg_Object *mspace;
Dwg_Object_Ref *mspace_ref;
Dwg_Object_BLOCK_HEADER *hdr;
const char *end;
char *p;
Dwg_Version_Type version = R_INVALID;
int i = 0;
int imperial = 0;
BITCODE_BL orig_num;
if (!dat->chain)
abort ();
end = (char *)&dat->chain[dat->size - 1];
if ((p = strstr ((char *)dat->chain, "\nimperial\n")))
{
imperial = 1;
p += strlen ("\nimperial\n");
}
if ((p = strstr ((char *)dat->chain, "version")))
{
int i_ver;
char s_ver[16];
i = sscanf (p, "version %d", &i_ver);
if (i)
{
snprintf (s_ver, 16, "r%d", i_ver);
s_ver[15] = '\0';
version = dwg_version_as (s_ver);
p += strlen ("version ");
}
else
p += strlen ("version ");
p = next_line (p, end);
}
if (!i || version >= R_AFTER)
version = R_2000;
dwg = dwg_new_Document (version, imperial, 0);
*dwgp = dwg;
mspace = dwg_model_space_object (dwg);
mspace_ref = dwg_model_space_ref (dwg);
hdr = mspace->tio.object->tio.BLOCK_HEADER;
orig_num = dwg->num_objects;
// read dat line by line and call the matching add API
while (p && p < end)
{
char text[120], s1[120];
dwg_point_2d p2, p3, p4;
dwg_point_3d pt1, pt2, pt3, pt4, scale;
double height, rot, len, f1, f2;
int i1, i2;
unsigned u;
Dwg_Entity_VIEWPORT *viewport = NULL;
Dwg_Entity_MTEXT *mtext = NULL;
Dwg_Object_DICTIONARY *dictionary = NULL;
Dwg_Object_XRECORD *xrecord = NULL;
Dwg_Object_MLINESTYLE *mlinestyle = NULL;
Dwg_Object_DIMSTYLE *dimstyle = NULL;
Dwg_Object_UCS *ucs = NULL;
Dwg_Object_LAYOUT *layout = NULL;
// accepts only ASCII strings, for fuzzing only
# ifdef HAVE_SSCANF_S
# define SSCANF_S sscanf_s
# define SZ , 119
# define FMT_NAME "%[a-zA-Z0-9_]"
# define FMT_TBL "\"%[a-zA-Z0-9._ -]\""
# define FMT_PATH "\"%[a-zA-Z0-9_. \\-]\""
# define FMT_ANY "\"%s\""
# else
# define SSCANF_S sscanf
# define SZ
# define FMT_NAME "%119[a-zA-Z0-9_]"
# define FMT_TBL "\"%119[a-zA-Z0-9._ -]\""
# define FMT_PATH "\"%119[a-zA-Z0-9_. \\-]\""
# define FMT_ANY "\"%119s\""
# endif
# define SET_ENT(var, name) \
if (!var) \
; \
else if (SSCANF_S (p, #var "." FMT_NAME " = %d", &s1[0] SZ, &i1)) \
dwg_dynapi_entity_set_value (var, #name, s1, &i1, 0); \
else if (SSCANF_S (p, #var "." FMT_NAME " = %lf", &s1[0] SZ, &f1)) \
dwg_dynapi_entity_set_value (var, #name, s1, &f1, 0); \
else if (SSCANF_S (p, #var "." FMT_NAME " = " FMT_ANY, &s1[0] SZ, \
&text[0] SZ)) \
dwg_dynapi_entity_set_value (var, #name, s1, text, 1)
if (SSCANF_S (p, "line (%lf %lf %lf) (%lf %lf %lf)", &pt1.x, &pt1.y,
&pt1.z, &pt2.x, &pt2.y, &pt2.z))
dwg_add_LINE (hdr, &pt1, &pt2);
else if (SSCANF_S (p, "ray (%lf %lf %lf) (%lf %lf %lf)", &pt1.x, &pt1.y,
&pt1.z, &pt2.x, &pt2.y, &pt2.z))
dwg_add_RAY (hdr, &pt1, &pt2);
else if (SSCANF_S (p, "xline (%lf %lf %lf) (%lf %lf %lf)", &pt1.x,
&pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z))
dwg_add_XLINE (hdr, &pt1, &pt2);
else if (SSCANF_S (p, "text " FMT_ANY " (%lf %lf %lf) %lf", &text[0] SZ,
&pt1.x, &pt1.y, &pt1.z, &height))
dwg_add_TEXT (hdr, text, &pt1, height);
else if (SSCANF_S (p, "mtext (%lf %lf %lf) %lf " FMT_ANY, &pt1.x, &pt1.y,
&pt1.z, &height, &text[0] SZ))
mtext = dwg_add_MTEXT (hdr, &pt1, height, text);
else
SET_ENT (mtext, MTEXT);
else if (SSCANF_S (p, "block " FMT_TBL, &text[0] SZ))
dwg_add_BLOCK (hdr, text);
else if (memBEGINc (p, "endblk\n")) dwg_add_ENDBLK (hdr);
else if (SSCANF_S (p, "insert (%lf %lf %lf) " FMT_TBL " %lf %lf %lf %lf",
&pt1.x, &pt1.y, &pt1.z, &text[0] SZ, &scale.x,
&scale.y, &scale.z, &rot))
dwg_add_INSERT (hdr, &pt1, text, scale.x, scale.y, scale.z,
deg2rad (rot));
else if (SSCANF_S (p,
"minsert (%lf %lf %lf) " FMT_TBL
" %lf %lf %lf %lf %d %d "
"%lf %lf",
&pt1.x, &pt1.y, &pt1.z, &text[0] SZ, &scale.x,
&scale.y, &scale.z, &rot, &i1, &i2, &f1, &f2))
dwg_add_MINSERT (hdr, &pt1, text, scale.x, scale.y, scale.z,
deg2rad (rot), i1, i2, f1, f2);
else if (SSCANF_S (p, "point (%lf %lf %lf)", &pt1.x, &pt1.y, &pt1.z))
dwg_add_POINT (hdr, &pt1);
else if (SSCANF_S (p, "circle (%lf %lf %lf) %lf", &pt1.x, &pt1.y, &pt1.z,
&f1)) dwg_add_CIRCLE (hdr, &pt1, f1);
else if (SSCANF_S (p, "arc (%lf %lf %lf) %lf %lf %lf", &pt1.x, &pt1.y,
&pt1.z, &f1, &f2, &height))
dwg_add_ARC (hdr, &pt1, f1, f2, height);
else if (SSCANF_S (p,
"dimension_aligned (%lf %lf %lf) (%lf %lf %lf) (%lf "
"%lf %lf)",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z,
&pt3.x, &pt3.y, &pt3.z))
dwg_add_DIMENSION_ALIGNED (hdr, &pt1, &pt2, &pt3);
else if (SSCANF_S (
p,
"dimension_linear (%lf %lf %lf) (%lf %lf %lf) (%lf %lf "
"%lf) %lf",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &pt3.x,
&pt3.y, &pt3.z, &rot))
dwg_add_DIMENSION_LINEAR (hdr, &pt1, &pt2, &pt3, deg2rad (rot));
else if (SSCANF_S (
p,
"dimension_ang2ln (%lf %lf %lf) (%lf %lf %lf) (%lf %lf "
"%lf) (%lf %lf %lf)",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &pt3.x,
&pt3.y, &pt3.z, &pt4.x, &pt4.y, &pt4.z))
dwg_add_DIMENSION_ANG2LN (hdr, &pt1, &pt2, &pt3, &pt4);
else if (SSCANF_S (
p,
"dimension_ang3pt (%lf %lf %lf) (%lf %lf %lf) (%lf %lf "
"%lf) (%lf %lf %lf)",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &pt3.x,
&pt3.y, &pt3.z, &pt4.x, &pt4.y, &pt4.z))
dwg_add_DIMENSION_ANG3PT (hdr, &pt1, &pt2, &pt3, &pt4);
else if (SSCANF_S (p,
"dimension_diameter (%lf %lf %lf) (%lf %lf %lf) %lf",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &len))
dwg_add_DIMENSION_DIAMETER (hdr, &pt1, &pt2, len);
else if (SSCANF_S (p,
"dimension_ordinate (%lf %lf %lf) (%lf %lf %lf) %d",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &i1))
dwg_add_DIMENSION_ORDINATE (hdr, &pt1, &pt2, i1 ? true : false);
else if (SSCANF_S (p, "dimension_radius (%lf %lf %lf) (%lf %lf %lf) %lf",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &len))
dwg_add_DIMENSION_RADIUS (hdr, &pt1, &pt2, len);
else if (SSCANF_S (p,
"3dface (%lf %lf %lf) (%lf %lf %lf) (%lf %lf "
"%lf) (%lf %lf %lf)",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z,
&pt3.x, &pt3.y, &pt3.z, &pt4.x, &pt4.y, &pt4.z))
dwg_add_3DFACE (hdr, &pt1, &pt2, &pt3, &pt4);
else if (SSCANF_S (p,
"3dface (%lf %lf %lf) (%lf %lf %lf) (%lf %lf "
"%lf)",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z,
&pt3.x, &pt3.y, &pt3.z))
dwg_add_3DFACE (hdr, &pt1, &pt2, &pt3, NULL);
else if (SSCANF_S (
p, "solid (%lf %lf %lf) (%lf %lf) (%lf %lf) (%lf %lf)",
&pt1.x, &pt1.y, &pt1.z, &p2.x, &p2.y, &p3.x, &p3.y, &p4.x,
&p4.y)) dwg_add_SOLID (hdr, &pt1, &p2, &p3, &p4);
else if (SSCANF_S (
p, "trace (%lf %lf %lf) (%lf %lf) (%lf %lf) (%lf %lf)",
&pt1.x, &pt1.y, &pt1.z, &p2.x, &p2.y, &p3.x, &p3.y, &p4.x,
&p4.y)) dwg_add_TRACE (hdr, &pt1, &p2, &p3, &p4);
else if (SSCANF_S (p, "polyline_2d %d ((%lf %lf)", &i1, &pt1.x, &pt1.y))
{
dwg_point_2d *pts = scan_pts2d (i1, &p);
if (i1 && pts)
{
dwg_add_POLYLINE_2D (hdr, i1, pts);
free (pts);
}
}
else if (SSCANF_S (p, "polyline_3d %d ((%lf %lf %lf)", &i1, &pt1.x,
&pt1.y, &pt1.z))
{
dwg_point_3d *pts = scan_pts3d (i1, &p);
if (i1 && pts)
{
dwg_add_POLYLINE_3D (hdr, i1, pts);
free (pts);
}
}
else if (SSCANF_S (p, "polyline_mesh %d %d ((%lf %lf %lf)", &i1, &i2,
&pt1.x, &pt1.y, &pt1.z))
{
dwg_point_3d *pts = scan_pts3d (i1 * i2, &p);
if (i1 && i2 && pts)
{
dwg_add_POLYLINE_MESH (hdr, i1, i2, pts);
free (pts);
}
}
else if (SSCANF_S (p, "dictionary " FMT_TBL " " FMT_TBL " %u",
&text[0] SZ, &s1[0] SZ, &u)) dictionary
= dwg_add_DICTIONARY (dwg, text, s1, (unsigned long)u);
else if (dictionary
&& SSCANF_S (p, "xrecord dictionary " FMT_TBL, &text[0] SZ))
xrecord
= dwg_add_XRECORD (dictionary, text);
else if (SSCANF_S (p, "shape " FMT_PATH " (%lf %lf %lf) %lf %lf",
&text[0] SZ, &pt1.x, &pt1.y, &pt1.z, &scale.x, &rot))
dwg_add_SHAPE (hdr, text, &pt1, scale.x, deg2rad (rot));
else if (SSCANF_S (p, "viewport " FMT_TBL, &text[0] SZ)) viewport
= dwg_add_VIEWPORT (hdr, text);
else SET_ENT (viewport, VIEWPORT);
else if (SSCANF_S (p, "ellipse (%lf %lf %lf) %lf %lf", &pt1.x, &pt1.y,
&pt1.z, &f1, &f2))
dwg_add_ELLIPSE (hdr, &pt1, f1, f2);
else if (SSCANF_S (p, "spline %d ((%lf %lf %lf)", &i1, &pt1.x, &pt1.y,
&pt1.z))
{
dwg_point_3d *fitpts = scan_pts3d (i1, &p);
if (i1 && fitpts
&& sscanf (p, ") (%lf %lf %lf) (%lf %lf %lf)", &pt2.x, &pt2.y,
&pt2.z, &pt3.x, &pt3.y, &pt3.z))
{
dwg_add_SPLINE (hdr, i1, fitpts, &pt2, &pt3);
}
free (fitpts);
}
else if (mtext
&& sscanf (p, "leader %d ((%lf %lf %lf)", &i1, &pt1.x, &pt1.y,
&pt1.z))
{
dwg_point_3d *pts = scan_pts3d (i1, &p);
if (i1 && pts && sscanf (p, ") mtext %d", &i2))
{
dwg_add_LEADER (hdr, i1, pts, mtext, i2);
}
free (pts);
}
else if (SSCANF_S (p,
"tolerance " FMT_TBL " (%lf %lf %lf) (%lf %lf %lf)",
&text[0] SZ, &pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y,
&pt2.z)) dwg_add_TOLERANCE (hdr, text, &pt1, &pt2);
else if (SSCANF_S (p, "mlinestyle " FMT_TBL, &text[0] SZ)) mlinestyle
= dwg_add_MLINESTYLE (dwg, text);
else if (SSCANF_S (p, "dimstyle " FMT_TBL, &text[0] SZ)) dimstyle
= dwg_add_DIMSTYLE (dwg, text);
else SET_ENT (mlinestyle, MLINESTYLE);
else SET_ENT (dimstyle, DIMSTYLE);
else if (SSCANF_S (
p, "ucs (%lf %lf %lf) (%lf %lf %lf) (%lf %lf %lf) " FMT_TBL,
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &pt3.x,
&pt3.y, &pt3.z, &text[0] SZ)) ucs
= dwg_add_UCS (dwg, &pt1, &pt2, &pt3, text);
else SET_ENT (ucs, UCS);
else if (viewport
&& SSCANF_S (p, "layout viewport " FMT_TBL " " FMT_ANY,
&text[0] SZ, &s1[0] SZ))
{
int error;
Dwg_Object *obj = dwg_ent_generic_to_object (viewport, &error);
if (!error)
layout = dwg_add_LAYOUT (obj, text, s1);
}
else if (SSCANF_S (p, "torus (%lf %lf %lf) (%lf %lf %lf) %lf %lf",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &f1,
&f2)) dwg_add_TORUS (hdr, &pt1, &pt2, f1, f2);
else if (SSCANF_S (p, "sphere (%lf %lf %lf) (%lf %lf %lf) %lf", &pt1.x,
&pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &f1))
dwg_add_SPHERE (hdr, &pt1, &pt2, f1);
else if (SSCANF_S (
p, "cylinder (%lf %lf %lf) (%lf %lf %lf) %lf %lf %lf %lf",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &height,
&f1, &f2, &len))
dwg_add_CYLINDER (hdr, &pt1, &pt2, height, f1, f2, len);
else if (SSCANF_S (p, "cone (%lf %lf %lf) (%lf %lf %lf) %lf %lf %lf %lf",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z,
&height, &f1, &f2, &len))
dwg_add_CONE (hdr, &pt1, &pt2, height, f1, f2, len);
else if (SSCANF_S (p, "wedge (%lf %lf %lf) (%lf %lf %lf) %lf %lf %lf",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &len,
&f1, &height))
dwg_add_WEDGE (hdr, &pt1, &pt2, len, f1, height);
else if (SSCANF_S (p, "box (%lf %lf %lf) (%lf %lf %lf) %lf %lf %lf",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z, &len,
&f1, &height))
dwg_add_BOX (hdr, &pt1, &pt2, len, f1, height);
else if (SSCANF_S (p,
"pyramid (%lf %lf %lf) (%lf %lf %lf) %lf %d %lf %lf",
&pt1.x, &pt1.y, &pt1.z, &pt2.x, &pt2.y, &pt2.z,
&height, &i1, &f1, &f2))
dwg_add_PYRAMID (hdr, &pt1, &pt2, height, i1, f1, f2);
else if (SSCANF_S (p, "HEADER." FMT_NAME " = %d", &s1[0] SZ, &i1))
dwg_dynapi_header_set_value (dwg, s1, &i1, 0);
else if (SSCANF_S (p, "HEADER." FMT_NAME " = %lf", &s1[0] SZ, &f1))
dwg_dynapi_header_set_value (dwg, s1, &f1, 0);
else if (SSCANF_S (p, "HEADER." FMT_NAME " = " FMT_ANY, &s1[0] SZ,
&text[0] SZ))
dwg_dynapi_header_set_value (dwg, s1, text, 1);
p = next_line (p, end);
}
// dwg_resolve_objectrefs_silent (orig_dwg);
// start fuzzing if at least 2 entities were added.
return (dwg->num_objects - orig_num > 2 ? 0 : 1);
}
#endif // USE_WRITE
@@ -0,0 +1,356 @@
/*****************************************************************************/
/* LibreDWG - free implementation of the DWG file format */
/* */
/* Copyright (C) 2021, 2023 Free Software Foundation, Inc. */
/* */
/* This library is free software, licensed under the terms of the GNU */
/* General Public License as published by the Free Software Foundation, */
/* either version 3 of the License, or (at your option) any later version. */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*****************************************************************************/
/*
* llvmfuzz.c: libfuzzer testing, esp. for oss-fuzz. with libfuzzer or
* standalone written by Reini Urban
*/
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
// #include <unistd.h>
#include <sys/stat.h>
#include "common.h"
#include <dwg.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#include "decode.h"
#include "encode.h"
#include "bits.h"
#ifndef DISABLE_DXF
# include "out_dxf.h"
# ifndef DISABLE_JSON
# include "in_json.h"
# include "out_json.h"
# endif
# include "in_dxf.h"
#endif
// Number of output converters selectable via out: 0 encode, 1 dxf, 2 dxfb, 3
// json, 4 geojson
#ifdef DISABLE_DXF
# define LLVMFUZZ_NUM_OUTPUTS 1
#else
# ifdef DISABLE_JSON
# define LLVMFUZZ_NUM_OUTPUTS 3
# else
# define LLVMFUZZ_NUM_OUTPUTS 5
# endif
#endif
int out;
int ver;
extern int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size);
// libfuzzer limitation:
// Enforce NULL-termination of the input buffer, to avoid bogus reports. copy
// it. Problematic is mostly strtol(3) which also works with \n termination.
static int
enforce_null_termination (Bit_Chain *dat, bool enforce)
{
unsigned char *copy;
unsigned char c;
if (!dat->size)
return 0;
c = dat->chain[dat->size - 1];
// Allow \n termination without \0 in DXF? No, still crashes
if (!enforce && ((c == '\n' && c + 1 == '\0') || c == '\0'))
return 0;
#ifdef STANDALONE
fprintf (stderr,
"llvmfuzz_standalone: enforce libfuzzer buffer NULL termination\n");
#endif
copy = malloc (dat->size + 1);
memcpy (copy, dat->chain, dat->size);
copy[dat->size] = '\0';
dat->chain = copy;
return 1;
}
int
LLVMFuzzerTestOneInput (const unsigned char *data, size_t size)
{
Dwg_Data dwg;
Bit_Chain dat = { NULL, 0, 0, 0, 0, 0, 0, NULL, 0 };
Bit_Chain out_dat = { NULL, 0, 0, 0, 0, 0, 0, NULL, 0 };
int copied = 0;
struct ly_ctx *ctx = NULL;
static char tmp_file[256];
#ifndef STANDALONE
/* The libfuzzer path otherwise leaves out/ver at 0, so only dwg_encode runs.
Derive them from the input (without consuming it, so the existing corpus
keeps decoding) to also drive the out_dxf/out_dxfb/out_json encoders. */
out = 0;
ver = 0;
if (size)
{
unsigned int h = 2166136261u;
const size_t n = size > 4096 ? 4096 : size;
for (size_t i = 0; i < n; i++)
h = (h ^ data[i]) * 16777619u;
out = (int)(h % LLVMFUZZ_NUM_OUTPUTS);
ver = (int)((h >> 8) % 20);
}
#endif
dat.chain = (unsigned char *)data;
dat.size = size;
memset (&dwg, 0, sizeof (dwg));
// Detect the input format: DWG, DXF or JSON
if (dat.size > 2 && dat.chain[0] == 'A' && dat.chain[1] == 'C')
{
if (dwg_decode (&dat, &dwg) >= DWG_ERR_CRITICAL)
{
dwg_free (&dwg);
return 0;
}
}
#ifndef DISABLE_JSON
else if (dat.size > 1 && dat.chain[0] == '{')
{
copied = enforce_null_termination (&dat, true);
if (dwg_read_json (&dat, &dwg) >= DWG_ERR_CRITICAL)
{
if (copied)
bit_chain_free (&dat);
dwg_free (&dwg);
return 0;
}
dat.opts |= DWG_OPTS_INJSON;
dwg.opts |= DWG_OPTS_INJSON;
}
#endif
#ifndef DISABLE_DXF
else
{
copied = enforce_null_termination (&dat, false);
if (dwg_read_dxf (&dat, &dwg) >= DWG_ERR_CRITICAL)
{
if (copied)
bit_chain_free (&dat);
dwg_free (&dwg);
return 0;
}
}
#else
else
return 0;
#endif
memset (&out_dat, 0, sizeof (out_dat));
bit_chain_set_version (&out_dat, &dat);
if (copied)
bit_chain_free (&dat);
#if 0
snprintf (tmp_file, 255, "/tmp/llvmfuzzer%d.out", getpid());
tmp_file[255] = '\0';
#elif defined _WIN32
strcpy (tmp_file, "NUL");
#else
strcpy (tmp_file, "/dev/null");
#endif
out_dat.fh = fopen (tmp_file, "w");
switch (out)
{
case 0:
{
switch (ver)
{
// TODO support preR13, many downconverters still missing
case 0:
out_dat.version = dwg.header.version = R_1_4;
break;
case 1:
out_dat.version = dwg.header.version = R_2_0;
break;
case 2:
out_dat.version = dwg.header.version = R_2_10;
break;
case 3:
out_dat.version = dwg.header.version = R_2_21;
break;
case 4:
out_dat.version = dwg.header.version = R_2_4;
break;
case 5:
out_dat.version = dwg.header.version = R_2_6;
break;
case 6:
out_dat.version = dwg.header.version = R_9;
break;
case 7:
out_dat.version = dwg.header.version = R_10;
break;
case 8:
out_dat.version = dwg.header.version = R_11;
break;
case 9:
out_dat.version = dwg.header.version = R_12;
break;
case 10:
out_dat.version = dwg.header.version = R_13;
break;
case 11:
out_dat.version = dwg.header.version = R_13c3;
break;
case 12:
out_dat.version = dwg.header.version = R_14;
break;
case 13:
out_dat.version = dwg.header.version = R_2004;
break;
default: // favor this one
out_dat.version = dwg.header.version = R_2000;
break;
}
dwg_encode (&dwg, &out_dat);
break;
}
#ifndef DISABLE_DXF
case 1:
dwg_write_dxf (&out_dat, &dwg);
break;
case 2: // experimental
dwg_write_dxfb (&out_dat, &dwg);
break;
# ifndef DISABLE_JSON
case 3:
dwg_write_json (&out_dat, &dwg);
break;
case 4:
dwg_write_geojson (&out_dat, &dwg);
break;
# endif
#endif
default:
break;
}
dwg_free (&dwg);
free (out_dat.chain);
fclose (out_dat.fh);
// unlink (tmp_file);
return 0;
}
#ifdef STANDALONE
/*
# ifdef __GNUC__
__attribute__((weak))
# endif
extern int LLVMFuzzerInitialize(int *argc, char ***argv);
*/
static int
usage (void)
{
printf ("\nUsage: OUT=0 VER=3 llvmfuzz_standalone INPUT...");
return 1;
}
// llvmfuzz_standalone reproducer, see OUT and VER env vars
int
main (int argc, char *argv[])
{
unsigned seed;
const unsigned int possible_outputformats = LLVMFUZZ_NUM_OUTPUTS;
if (argc <= 1 || !*argv[1])
return usage ();
if (getenv ("SEED"))
seed = (unsigned)strtol (getenv ("SEED"), NULL, 10) % 9999;
else
{
# ifdef HAVE_GETTIMEOFDAY
struct timeval tval;
gettimeofday (&tval, NULL);
seed = (unsigned)(tval.tv_sec * 1000 + tval.tv_usec) % 9999;
# else
seed = (unsigned)time (NULL) % 9999;
# endif
}
srand (seed);
/* works only on linux
if (LLVMFuzzerInitialize)
LLVMFuzzerInitialize (&argc, &argv);
*/
for (int i = 1; i < argc; i++)
{
unsigned char *buf;
FILE *f = fopen (argv[i], "rb");
struct stat attrib;
long len;
size_t n_read;
int fd;
if (!f)
{
fprintf (stderr, "Illegal file argument %s\n", argv[i]);
continue;
}
fd = fileno (f);
if (fd < 0 || fstat (fd, &attrib)
|| !(S_ISREG (attrib.st_mode)
# ifndef _WIN32
|| S_ISLNK (attrib.st_mode)
# endif
))
{
fprintf (stderr, "Illegal input file \"%s\"\n", argv[i]);
continue;
}
// libFuzzer design bug, not zero-terminating its text buffer
fseek (f, 0, SEEK_END);
len = ftell (f);
fseek (f, 0, SEEK_SET);
if (len <= 0)
continue;
buf = (unsigned char *)malloc (len);
n_read = fread (buf, 1, len, f);
fclose (f);
assert ((long)n_read == len);
out = rand () % possible_outputformats;
# ifdef STANDALONE
if (getenv ("OUT"))
out = strtol (getenv ("OUT"), NULL, 10);
// print SEED onlyu when needed (no env vars given)
if (!(out || getenv ("VER")))
fprintf (stderr, "SEED=%04u ", seed);
fprintf (stderr, "OUT=%d ", out);
# endif
if (out == 0)
{
ver = rand () % 20;
# ifdef STANDALONE
if (getenv ("VER"))
ver = strtol (getenv ("VER"), NULL, 10);
fprintf (stderr, "VER=%d ", ver);
# endif
}
fprintf (stderr, "examples/llvmfuzz_standalone %s [%" PRIuSIZE "]\n",
argv[i], len);
LLVMFuzzerTestOneInput (buf, len);
free (buf);
// Bit_Chain dat = { 0 };
// dat_read_file (&dat, fp, argv[i]);
// LLVMFuzzerTestOneInput (dat.chain, dat.size);
// bit_free_chain (&dat);
}
}
#endif
@@ -0,0 +1,230 @@
/*****************************************************************************/
/* LibreDWG - free implementation of the DWG file format */
/* */
/* Copyright (C) 2009, 2023 Free Software Foundation, Inc. */
/* Copyright (C) 2010 Thien-Thi Nguyen */
/* */
/* This library is free software, licensed under the terms of the GNU */
/* General Public License as published by the Free Software Foundation, */
/* either version 3 of the License, or (at your option) any later version. */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*****************************************************************************/
/*
* load_dwg.c: load a DWG, get lines, text and circles
* written by Felipe Castro
* modified by Felipe Corrêa da Silva Sances
* modified by Thien-Thi Nguyen
* modified by Reini Urban
*/
#include "config.h"
#ifdef __STDC_ALLOC_LIB__ /* for strdup */
# define __STDC_WANT_LIB_EXT2__ 1 /* for strdup */
#else
# define _USE_BSD 1
#endif
#ifndef _XOPEN_SOURCE /* for strdup, snprintf */
# define _XOPEN_SOURCE 700
#endif
#include <stdio.h>
#include <time.h>
#ifndef DISABLE_WRITE
# include <sys/stat.h>
# include <unistd.h>
#endif
#include "dwg.h"
#include "dwg_api.h"
#include "../src/bits.h"
#include "../programs/suffix.inc"
static int help (void);
int verbosity (int argc, char **argv, int i, unsigned int *opts);
#include "../programs/common.inc"
static int
usage (void)
{
printf ("\nUsage: load_dwg [-v[0-9]] DWGFILE\n");
return 1;
}
static int
opt_version (void)
{
printf ("load_dwg %s\n", PACKAGE_VERSION);
return 0;
}
static int
help (void)
{
printf ("\nUsage: load_dwg [OPTION]... DWGFILE\n");
printf ("Example to add fingerprint elements to a DWG.\n"
"\n");
printf (" -v[0-9], --verbose [0-9] verbosity\n");
printf (" --help display this help and exit\n");
printf (" --version output version information and exit\n"
"\n");
printf ("GNU LibreDWG online manual: "
"<https://www.gnu.org/software/libredwg/>\n");
return 0;
}
static int
add_fingerprint (Dwg_Data *dwg, dwg_point_3d *pt)
{
int error = 0;
char text[128];
double height = dwg->header_vars.TEXTSIZE;
time_t t = time (NULL);
Dwg_Object_BLOCK_HEADER *hdr = dwg_get_block_header (dwg, &error);
Dwg_Entity_TEXT *ent;
if (error)
return 1;
#ifdef HAVE_WFORMAT_Y2K
GCC46_DIAG_IGNORE (-Wformat-y2k)
#endif
strftime (text, sizeof (text), "Last updated: %c", localtime (&t));
#ifdef HAVE_WFORMAT_Y2K
GCC46_DIAG_RESTORE
#endif
#ifdef USE_WRITE
if ((ent = dwg_add_TEXT (hdr, text, pt, height)))
{
ent->horiz_alignment = HORIZ_ALIGNMENT_RIGHT;
return 0;
}
else
#endif
return 1;
}
static int
change_fingerprint (Dwg_Data *dwg, Dwg_Entity_TEXT *_obj)
{
char text[128];
double height = dwg->header_vars.TEXTSIZE;
time_t t = time (NULL);
#ifdef HAVE_WFORMAT_Y2K
GCC46_DIAG_IGNORE (-Wformat-y2k)
#endif
strftime (text, sizeof (text), "Last updated: %c", localtime (&t));
#ifdef HAVE_WFORMAT_Y2K
GCC46_DIAG_RESTORE
#endif
if (dwg->header.version < R_2007)
{
if (strlen (text) < strlen (_obj->text_value))
strcpy (_obj->text_value, text);
else
{
free (_obj->text_value);
_obj->text_value = strdup (text);
}
}
else
{
free (_obj->text_value);
_obj->text_value = (BITCODE_TV)bit_utf8_to_TU (text, 0);
}
return 0;
}
static int
load_dwg (char *filename, unsigned int opts)
{
BITCODE_BL i;
int success, found = 0;
Dwg_Data dwg;
dwg_point_3d pt;
#ifdef USE_WRITE
char *new_filename = (char *)malloc (strlen (filename) + 4);
char *fn = strdup (filename);
char *base = basename (fn);
char *p;
struct stat st;
if ((p = strrchr (base, '.')))
*p = '\0';
sprintf (new_filename, "%s_new.dwg", base);
free (fn);
#endif
memset (&dwg, 0, sizeof (Dwg_Data));
dwg.opts = opts;
success = dwg_read_file (filename, &dwg);
// get the insertion point for our fingerprint
pt.x = dwg.header_vars.LIMMAX.x;
pt.y = dwg.header_vars.LIMMAX.y;
pt.z = 0.0;
// check if a fingerprint already exists there.
// if so update it. if not add it
for (i = 0; i < dwg.num_objects; i++)
{
if (dwg.object[i].fixedtype == DWG_TYPE_TEXT)
{
Dwg_Entity_TEXT *_obj = dwg.object[i].tio.entity->tio.TEXT;
if (pt.x == _obj->ins_pt.x && pt.y == _obj->ins_pt.y
&& _obj->horiz_alignment == HORIZ_ALIGNMENT_RIGHT)
{
found++;
change_fingerprint (&dwg, _obj);
fprintf (stderr, "fingerprint updated at (%f, %f)\n", pt.x,
pt.y);
}
}
}
if (!found)
{
add_fingerprint (&dwg, &pt);
fprintf (stderr, "fingerprint added at (%f, %f)\n", pt.x, pt.y);
}
#ifdef USE_WRITE
if (0 == stat (new_filename, &st))
unlink (new_filename);
if (dwg.header.version > R_2000)
dwg.header.version = R_2000;
success = dwg_write_file (new_filename, &dwg);
free (new_filename);
#endif
dwg_free (&dwg);
return success;
}
int
main (int argc, char *argv[])
{
int i = 1;
unsigned int opts = 0;
if (argc < 2)
return usage ();
#if defined(USE_TRACING) && defined(HAVE_SETENV)
setenv ("LIBREDWG_TRACE", "1", 0);
#endif
if (argc > 2
&& (!strcmp (argv[i], "--verbose") || !strncmp (argv[i], "-v", 2)))
{
int num_args = verbosity (argc, argv, i, &opts);
argc -= num_args;
i += num_args;
}
if (argc > 1 && !strcmp (argv[i], "--help"))
return help ();
if (argc > 1 && !strcmp (argv[i], "--version"))
return opt_version ();
REQUIRE_INPUT_FILE_ARG (argc);
load_dwg (argv[i], opts);
return 0;
}
@@ -0,0 +1,76 @@
/*****************************************************************************/
/* LibreDWG - free implementation of the DWG file format */
/* */
/* Copyright (C) 2023 Free Software Foundation, Inc. */
/* */
/* This library is free software, licensed under the terms of the GNU */
/* General Public License as published by the Free Software Foundation, */
/* either version 3 of the License, or (at your option) any later version. */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/*****************************************************************************/
/*
* odaversion.c: prints the ODA compatible DWG version
* modified by Reini Urban
oda understands those versions:
"ACAD9","ACAD10","ACAD12",
"ACAD13","ACAD14",
"ACAD2000","ACAD2004",
"ACAD2007","ACAD2010",
"ACAD2013","ACAD2018"
*/
#include <stdio.h>
int
main (int argc, char *argv[])
{
FILE *f;
char buf[7];
size_t size;
if (argc != 2)
return 1;
f = fopen (argv[1], "rb");
if (!f)
return 1;
size = fread (buf, 1, 6, f);
if (size != 6)
return 1;
buf[6] = '\0';
if (buf[0] == 'A' && buf[1] == 'C')
{
int v;
if (1 != sscanf (buf, "AC%4d", &v))
return 1;
if (v < 1004) // <r9
printf ("\n");
else if (v <= 1005)
printf ("9\n");
else if (v <= 1006)
printf ("10\n");
else if (v <= 1009)
printf ("11\n");
else if (v <= 1012)
printf ("13\n");
else if (v <= 1014)
printf ("14\n");
else if (v <= 1016)
printf ("2000\n");
else if (v <= 1018)
printf ("2004\n");
else if (v <= 1021)
printf ("2007\n");
else if (v <= 1024)
printf ("2010\n");
else if (v < 1028)
printf ("2013\n");
else if (v < 1033)
printf ("2018\n");
else
printf ("\n");
}
fclose (f);
return 0;
}
@@ -0,0 +1,15 @@
#include <stdio.h>
#include <stdlib.h>
unsigned char block[255];
main ()
{
int len, err;
while (!feof (stdin))
{
len = fread (block, 1, 255, stdin);
err = rs_decode_block (block, 1);
fwrite (block, 1, 239, stdout);
}
}
@@ -0,0 +1,28 @@
#include <stdio.h>
#include <stdlib.h>
unsigned char data[239];
unsigned char parity[16];
main ()
{
int len, i;
while (!feof (stdin))
{
len = fread (data, 1, 239, stdin);
rs_encode_block (parity, data, len);
for (i = 0; i < 16; i++)
{
if (i != 0)
fputc ('-', stderr);
}
fwrite (data, 1, len, stdout);
while (len++ < 239)
fputc (0, stdout); /* Pad data with 0s */
fwrite (parity, 16, 1, stdout);
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,41 @@
# libredwg.la - a libtool library file
# Generated by libtool (GNU libtool) 2.5.4
#
# Please DO NOT delete this file!
# It is necessary for linking the library.
# The name that we can dlopen(3).
dlname='../bin/libredwg-0.dll'
# Names of this library.
library_names='libredwg.dll.a'
# The name of the static archive.
old_library=''
# Linker flags that cannot go in dependency_libs.
inherited_linker_flags=''
# Libraries that this one depends upon.
dependency_libs=''
# Names of additional weak libraries provided by this library
weak_library_names=''
# Version information for libredwg.
current=0
age=0
revision=14
# Is this an already installed library?
installed=yes
# Should we warn about portability when linking against -modules?
shouldnotlink=no
# Files to dlopen/dlpreopen
dlopen=''
dlpreopen=''
# Directory that this library needs to be installed in:
libdir='/lib'
@@ -0,0 +1,12 @@
# libredwg.pc. Generated from libredwg.pc.in by configure.
prefix=
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: libredwg
Description: Free implementation of the DWG file format
Version: 0.14
URL: https://savannah.gnu.org/projects/libredwg/
Libs: -L${libdir} -lredwg -lm
Cflags: -I${includedir}
@@ -0,0 +1,41 @@
# _LibreDWG.la - a libtool library file
# Generated by libtool (GNU libtool) 2.5.4
#
# Please DO NOT delete this file!
# It is necessary for linking the library.
# The name that we can dlopen(3).
dlname='_LibreDWG.dll'
# Names of this library.
library_names='_LibreDWG.dll.a'
# The name of the static archive.
old_library=''
# Linker flags that cannot go in dependency_libs.
inherited_linker_flags=''
# Libraries that this one depends upon.
dependency_libs=' /lib/libredwg.la -L=C:/msys64/MINGW64/lib -lpython3.12 -lversion -lshlwapi -lpathcch -lbcrypt'
# Names of additional weak libraries provided by this library
weak_library_names=''
# Version information for _LibreDWG.
current=0
age=0
revision=0
# Is this an already installed library?
installed=yes
# Should we warn about portability when linking against -modules?
shouldnotlink=yes
# Files to dlopen/dlpreopen
dlopen=''
dlpreopen=''
# Directory that this library needs to be installed in:
libdir='/lib/python3.12/site-packages'
@@ -0,0 +1,561 @@
This is LibreDWG.info, produced by makeinfo version 7.2 from
LibreDWG.texi.
This manual is for GNU LibreDWG (version 0.14, 27 June 2026).
Copyright © 2010-2026 Free Software Foundation, Inc.
Permission is granted to copy, distribute and/or modify this
document under the terms of the GNU Free Documentation License,
Version 1.3 or any later version published by the Free Software
Foundation; with no Invariant Sections, with no Front-Cover Texts,
and with no Back-Cover Texts. A copy of the license is included in
the section entitled "GNU Free Documentation License".
All other trademarks, trade names or company names referenced herein
are used for identification purposes only and are the property of their
respective owners.
DWG is the native and proprietary file format for AutoCAD® and a
trademark of Autodesk, Inc. LibreDWG is neither associated with
Autodesk, nor with the Open Design Alliance.
INFO-DIR-SECTION Libraries
START-INFO-DIR-ENTRY
* LibreDWG: (LibreDWG). Free implementation of the DWG file format.
END-INFO-DIR-ENTRY

Indirect:
LibreDWG.info-1: 1095
LibreDWG.info-2: 303901

Tag Table:
(Indirect)
Node: Top1095
Node: Overview2721
Node: Usage10452
Node: Types11183
Node: Objects13909
Node: HEADER14200
Node: ENTITIES30247
Ref: 3DFACE30440
Ref: 3DLINE30786
Ref: 3DSOLID31018
Ref: ALIGNMENTPARAMETERENTITY32363
Ref: ARC32429
Ref: ARCALIGNEDTEXT32756
Ref: ARC_DIMENSION34036
Ref: ATTDEF35421
Ref: ATTRIB36733
Ref: BASEPOINTPARAMETERENTITY38032
Ref: BLOCK38100
Ref: BODY38286
Ref: CAMERA38321
Ref: CIRCLE38418
Ref: DGNUNDERLAY38656
Ref: UNDERLAY38656
Ref: DIMENSION_ALIGNED39243
Ref: DIMENSION_ANG2LN40375
Ref: DIMENSION_ANG3PT41563
Ref: DIMENSION_DIAMETER42732
Ref: DIMENSION_LINEAR43821
Ref: DIMENSION_ORDINATE45000
Ref: DIMENSION_RADIUS46129
Ref: DWFUNDERLAY47213
Ref: ELLIPSE47250
Ref: ENDBLK47572
Ref: ENDREP47641
Ref: EXTRUDEDSURFACE47885
Ref: FLIPGRIPENTITY50489
Ref: FLIPPARAMETERENTITY50571
Ref: GEOPOSITIONMARKER50651
Ref: HATCH51401
Ref: HELIX52654
Ref: IMAGE53951
Ref: INSERT54708
Ref: JUMP55444
Ref: LARGE_RADIAL_DIMENSION55668
Ref: LAYOUTPRINTCONFIG56837
Ref: LEADER56981
Ref: LIGHT58131
Ref: LINE60261
Ref: LINEARGRIPENTITY60572
Ref: LINEARPARAMETERENTITY60656
Ref: LOAD60723
Ref: LOFTEDSURFACE60840
Ref: LWPOLYLINE63218
Ref: MESH63818
Ref: MINSERT64479
Ref: MLINE65174
Ref: MPOLYGON65626
Ref: MTEXT66784
Ref: MULTILEADER68234
Ref: NAVISWORKSMODEL70082
Ref: NURBSURFACE70325
Ref: OLE2FRAME71987
Ref: OLEFRAME72414
Ref: PDFUNDERLAY72634
Ref: PLANESURFACE72676
Ref: POINT74181
Ref: POINTCLOUD74479
Ref: POINTCLOUDEX75499
Ref: POINTPARAMETERENTITY77032
Ref: POLARGRIPENTITY77110
Ref: POLYLINE_2D77184
Ref: POLYLINE_3D77917
Ref: POLYLINE_MESH78410
Ref: POLYLINE_PFACE78947
Ref: PROXY_ENTITY79349
Ref: RAY79942
Ref: REGION80090
Ref: REPEAT80125
Ref: REVOLVEDSURFACE80203
Ref: ROTATIONGRIPENTITY82280
Ref: ROTATIONPARAMETERENTITY82366
Ref: RTEXT82434
Ref: SECTIONOBJECT82784
Ref: SEQEND83409
Ref: SHAPE83516
Ref: SOLID83955
Ref: SPLINE84315
Ref: SWEPTSURFACE85173
Ref: TABLE87891
Ref: TEXT94167
Ref: TOLERANCE94880
Ref: TRACE95247
Ref: UNKNOWN_ENT95612
Ref: VERTEX_2D95684
Ref: VERTEX_3D96035
Ref: VERTEX_MESH96185
Ref: VERTEX_PFACE96228
Ref: VERTEX_PFACE_FACE96276
Ref: VIEWPORT96425
Ref: VISIBILITYGRIPENTITY98425
Ref: VISIBILITYPARAMETERENTITY98513
Ref: WIPEOUT98583
Ref: XLINE99339
Ref: XYGRIPENTITY99376
Ref: XYPARAMETERENTITY99456
Node: OBJECTS99514
Ref: ACMECOMMANDHISTORY99706
Ref: ACMESCOPE99816
Ref: ACMESTATEMGR99929
Ref: ACSH_BOOLEAN_CLASS100048
Ref: ACSH_BOX_CLASS100425
Ref: ACSH_BREP_CLASS100793
Ref: ACSH_CHAMFER_CLASS102306
Ref: ACSH_CONE_CLASS102798
Ref: ACSH_CYLINDER_CLASS103224
Ref: ACSH_EXTRUSION_CLASS103651
Ref: ACSH_FILLET_CLASS104981
Ref: ACSH_HISTORY_CLASS105631
Ref: ACSH_LOFT_CLASS105956
Ref: ACSH_PYRAMID_CLASS106367
Ref: ACSH_REVOLVE_CLASS106780
Ref: ACSH_SPHERE_CLASS107492
Ref: ACSH_SWEEP_CLASS107784
Ref: ACSH_TORUS_CLASS109113
Ref: ACSH_WEDGE_CLASS109456
Ref: ALDIMOBJECTCONTEXTDATA109831
Ref: ANGDIMOBJECTCONTEXTDATA110137
Ref: ANNOTSCALEOBJECTCONTEXTDATA110443
Ref: APPID110638
Ref: APPID_CONTROL111010
Ref: ASSOC2DCONSTRAINTGROUP111250
Ref: ASSOC3POINTANGULARDIMACTIONBODY112193
Ref: ASSOCACTION112661
Ref: ASSOCACTIONPARAM113266
Ref: ASSOCALIGNEDDIMACTIONBODY113474
Ref: ASSOCARRAYACTIONBODY113910
Ref: ASSOCARRAYMODIFYACTIONBODY114229
Ref: ASSOCASMBODYACTIONPARAM114675
Ref: ASSOCBLENDSURFACEACTIONBODY116237
Ref: ASSOCCOMPOUNDACTIONPARAM116823
Ref: ASSOCDEPENDENCY117469
Ref: ASSOCDIMDEPENDENCYBODY118160
Ref: ASSOCEDGEACTIONPARAM118416
Ref: ASSOCEDGECHAMFERACTIONBODY118914
Ref: ASSOCEDGEFILLETACTIONBODY119198
Ref: ASSOCEXTENDSURFACEACTIONBODY119485
Ref: ASSOCEXTRUDEDSURFACEACTIONBODY119860
Ref: ASSOCFACEACTIONPARAM120185
Ref: ASSOCFILLETSURFACEACTIONBODY120568
Ref: ASSOCGEOMDEPENDENCY121005
Ref: ASSOCLOFTEDSURFACEACTIONBODY121340
Ref: ASSOCMLEADERACTIONBODY121667
Ref: ASSOCNETWORK122118
Ref: ASSOCNETWORKSURFACEACTIONBODY123029
Ref: ASSOCOBJECTACTIONPARAM123356
Ref: ASSOCOFFSETSURFACEACTIONBODY123701
Ref: ASSOCORDINATEDIMACTIONBODY124067
Ref: ASSOCOSNAPPOINTREFACTIONPARAM124512
Ref: ASSOCPATCHSURFACEACTIONBODY125290
Ref: ASSOCPATHACTIONPARAM125615
Ref: ASSOCPERSSUBENTMANAGER126308
Ref: ASSOCPLANESURFACEACTIONBODY128253
Ref: ASSOCPOINTREFACTIONPARAM128582
Ref: ASSOCRESTOREENTITYSTATEACTIONBODY129246
Ref: ASSOCREVOLVEDSURFACEACTIONBODY129468
Ref: ASSOCROTATEDDIMACTIONBODY129798
Ref: ASSOCSWEPTSURFACEACTIONBODY130241
Ref: ASSOCTRIMSURFACEACTIONBODY130572
Ref: ASSOCVALUEDEPENDENCY131008
Ref: ASSOCVARIABLE131141
Ref: ASSOCVERTEXACTIONPARAM132099
Ref: BLKREFOBJECTCONTEXTDATA132475
Ref: BLOCKALIGNEDCONSTRAINTPARAMETER132825
Ref: BLOCKALIGNMENTGRIP133978
Ref: BLOCKALIGNMENTPARAMETER134551
Ref: BLOCKANGULARCONSTRAINTPARAMETER135542
Ref: BLOCKARRAYACTION136838
Ref: BLOCKBASEPOINTPARAMETER137495
Ref: BLOCKDIAMETRICCONSTRAINTPARAMETER138168
Ref: BLOCKFLIPACTION139370
Ref: BLOCKFLIPGRIP140043
Ref: BLOCKFLIPPARAMETER140722
Ref: BLOCKGRIPLOCATIONCOMPONENT141973
Ref: BLOCKHORIZONTALCONSTRAINTPARAMETER142197
Ref: BLOCKLINEARCONSTRAINTPARAMETER143362
Ref: BLOCKLINEARGRIP144512
Ref: BLOCKLINEARPARAMETER145082
Ref: BLOCKLOOKUPACTION146195
Ref: BLOCKLOOKUPGRIP146935
Ref: BLOCKLOOKUPPARAMETER147457
Ref: BLOCKMOVEACTION148190
Ref: BLOCKPARAMDEPENDENCYBODY148892
Ref: BLOCKPOINTPARAMETER149147
Ref: BLOCKPOLARGRIP149860
Ref: BLOCKPOLARPARAMETER150381
Ref: BLOCKPOLARSTRETCHACTION151653
Ref: BLOCKPROPERTIESTABLE152494
Ref: BLOCKPROPERTIESTABLEGRIP152581
Ref: BLOCKRADIALCONSTRAINTPARAMETER153113
Ref: BLOCKREPRESENTATION154270
Ref: BLOCKROTATEACTION154425
Ref: BLOCKROTATIONGRIP155112
Ref: BLOCKROTATIONPARAMETER155636
Ref: BLOCKSCALEACTION156798
Ref: BLOCKSTRETCHACTION157486
Ref: BLOCKUSERPARAMETER158460
Ref: BLOCKVERTICALCONSTRAINTPARAMETER159252
Ref: BLOCKVISIBILITYGRIP160406
Ref: BLOCKVISIBILITYPARAMETER160932
Ref: BLOCKXYGRIP161877
Ref: BLOCKXYPARAMETER162395
Ref: BLOCK_CONTROL163680
Ref: BLOCK_HEADER163980
Ref: BREAKDATA165294
Ref: BREAKPOINTREF165498
Ref: CELLSTYLEMAP165573
Ref: CONTEXTDATAMANAGER165749
Ref: CSACDOCUMENTOPTIONS165957
Ref: CURVEPATH166067
Ref: DATALINK166223
Ref: DATATABLE167177
Ref: DBCOLOR167456
Ref: DETAILVIEWSTYLE167573
Ref: DICTIONARY169292
Ref: DICTIONARYVAR169570
Ref: DICTIONARYWDFLT169727
Ref: DIMASSOC170042
Ref: DIMSTYLE170378
Ref: DIMSTYLE_CONTROL174070
Ref: DMDIMOBJECTCONTEXTDATA174406
Ref: DUMMY174736
Ref: DYNAMICBLOCKPROXYNODE174820
Ref: DYNAMICBLOCKPURGEPREVENTER174952
Ref: EVALUATION_GRAPH175097
Ref: FCFOBJECTCONTEXTDATA175522
Ref: FIELD175802
Ref: FIELDLIST176682
Ref: GEODATA176866
Ref: GEOMAPIMAGE178721
Ref: GRADIENT_BACKGROUND179768
Ref: GROUND_PLANE_BACKGROUND180158
Ref: GROUP180578
Ref: IBL_BACKGROUND180850
Ref: IDBUFFER181181
Ref: IMAGEDEF181361
Ref: IMAGEDEF_REACTOR181698
Ref: IMAGE_BACKGROUND181823
Ref: INDEX182206
Ref: LAYER182324
Ref: LAYERFILTER183090
Ref: LAYER_CONTROL183238
Ref: LAYER_INDEX183467
Ref: LAYOUT183668
Ref: LEADEROBJECTCONTEXTDATA184636
Ref: LIGHTLIST185106
Ref: LONG_TRANSACTION185323
Ref: LTYPE185391
Ref: LTYPE_CONTROL186093
Ref: MATERIAL186381
Ref: MENTALRAYRENDERSETTINGS188206
Ref: MLEADEROBJECTCONTEXTDATA191009
Ref: MLEADERSTYLE191211
Ref: MLINESTYLE193258
Ref: MOTIONPATH193665
Ref: MTEXTATTRIBUTEOBJECTCONTEXTDATA194062
Ref: MTEXTOBJECTCONTEXTDATA194542
Ref: NAVISWORKSMODELDEF195372
Ref: OBJECT_PTR195701
Ref: ORDDIMOBJECTCONTEXTDATA195787
Ref: PARTIAL_VIEWING_INDEX196146
Ref: PERSUBENTMGR196354
Ref: PLACEHOLDER196818
Ref: PLOTSETTINGS196893
Ref: POINTCLOUDCOLORMAP198272
Ref: POINTCLOUDDEF198813
Ref: POINTCLOUDDEFEX199159
Ref: POINTCLOUDDEF_REACTOR199511
Ref: POINTCLOUDDEF_REACTOR_EX199644
Ref: POINTPATH199762
Ref: PROXY_OBJECT199922
Ref: RADIMLGOBJECTCONTEXTDATA200428
Ref: RADIMOBJECTCONTEXTDATA200776
Ref: RAPIDRTRENDERSETTINGS201082
Ref: RASTERVARIABLES201997
Ref: RENDERENTRY202245
Ref: RENDERENVIRONMENT203123
Ref: RENDERGLOBAL203781
Ref: RENDERSETTINGS204268
Ref: SCALE204808
Ref: SECTIONVIEWSTYLE205099
Ref: SECTION_MANAGER207330
Ref: SECTION_SETTINGS207535
Ref: SKYLIGHT_BACKGROUND207754
Ref: SOLID_BACKGROUND207917
Ref: SORTENTSTABLE208078
Ref: SPATIAL_FILTER208302
Ref: SPATIAL_INDEX208879
Ref: STYLE209199
Ref: STYLE_CONTROL209901
Ref: SUN210122
Ref: SUNSTUDY210661
Ref: TABLECONTENT211932
Ref: TABLEGEOMETRY212190
Ref: TABLESTYLE212436
Ref: TEXTOBJECTCONTEXTDATA213298
Ref: TVDEVICEPROPERTIES213681
Ref: UCS214082
Ref: UCS_CONTROL214783
Ref: UNKNOWN_OBJ215010
Ref: VBA_PROJECT215084
Ref: VIEW215231
Ref: VIEW_CONTROL217000
Ref: VISUALSTYLE217228
Ref: VPORT223233
Ref: VPORT_CONTROL225472
Ref: VX_CONTROL225700
Ref: VX_TABLE_RECORD225930
Ref: WIPEOUTVARIABLES226532
Ref: XRECORD226648
Ref: PDFDEFINITION226962
Ref: UNDERLAYDEFINITION226962
Ref: DGNDEFINITION227112
Ref: DWFDEFINITION227165
Ref: ASSOCARRAYMODIFYPARAMETERS227231
Ref: ASSOCARRAYPARAMETERS227231
Ref: ASSOCARRAYPATHPARAMETERS227568
Ref: ASSOCARRAYPOLARPARAMETERS227635
Ref: ASSOCARRAYRECTANGULARPARAMETERS227708
Ref: Dwg_3DSOLID_material227770
Ref: Dwg_3DSOLID_silhouette227966
Ref: Dwg_3DSOLID_wire228340
Ref: Dwg_ACSH_HistoryNode228941
Ref: Dwg_ACSH_SubentColor229163
Ref: Dwg_ACSH_SubentMaterial229360
Ref: Dwg_ACTIONBODY229515
Ref: Dwg_ARRAYITEMLOCATOR229703
Ref: Dwg_ASSOCACTIONBODY_action229844
Ref: Dwg_ASSOCACTION_Deps230009
Ref: Dwg_ASSOCARRAYITEM230154
Ref: Dwg_ASSOCPARAMBASEDACTIONBODY230615
Ref: Dwg_ASSOCSURFACEACTIONBODY231001
Ref: Dwg_AcDbMTextObjectEmbedded231290
Ref: Dwg_AcDs233055
Ref: Dwg_AcDs_Data234002
Ref: Dwg_AcDs_DataBlob234136
Ref: Dwg_AcDs_DataBlob01234418
Ref: Dwg_AcDs_DataBlobRef234685
Ref: Dwg_AcDs_DataBlobRef_Page234985
Ref: Dwg_AcDs_DataIndex235071
Ref: Dwg_AcDs_DataIndex_Entry235229
Ref: Dwg_AcDs_Data_Record235350
Ref: Dwg_AcDs_Data_RecordHdr235445
Ref: Dwg_AcDs_Schema235598
Ref: Dwg_AcDs_SchemaData235774
Ref: Dwg_AcDs_SchemaData_UProp236052
Ref: Dwg_AcDs_SchemaIndex236139
Ref: Dwg_AcDs_SchemaIndex_Prop236468
Ref: Dwg_AcDs_Schema_Prop236588
Ref: Dwg_AcDs_Search236870
Ref: Dwg_AcDs_Search_Data236983
Ref: Dwg_AcDs_Search_IdIdx237244
Ref: Dwg_AcDs_Search_IdIdxs237372
Ref: Dwg_AcDs_Segment237480
Ref: Dwg_AcDs_SegmentIndex237924
Ref: Dwg_BLOCKACTION_connectionpts238022
Ref: Dwg_BLOCKLOOKUPACTION_lut238113
Ref: Dwg_BLOCKPARAMETER_PropInfo238348
Ref: Dwg_BLOCKPARAMETER_connection238489
Ref: Dwg_BLOCKPARAMVALUESET238576
Ref: Dwg_BLOCKSTRETCHACTION_codes238842
Ref: Dwg_BLOCKSTRETCHACTION_handles239069
Ref: Dwg_BLOCKVISIBILITYPARAMETER_state239299
Ref: Dwg_COMPOUNDOBJECTID239603
Ref: Dwg_CONSTRAINTGROUPNODE239781
Ref: Dwg_CONTEXTDATA_dict240019
Ref: Dwg_CONTEXTDATA_submgr240187
Ref: Dwg_CellContentGeometry240410
Ref: Dwg_CellStyle240854
Ref: Dwg_ColorRamp241760
Ref: Dwg_ContentFormat241977
Ref: Dwg_DATALINK_customdata242518
Ref: Dwg_DATATABLE_column242670
Ref: Dwg_DATATABLE_row242870
Ref: Dwg_DIMASSOC_Ref242995
Ref: Dwg_DIMENSION_common243829
Ref: Dwg_EVAL_Edge244826
Ref: Dwg_EVAL_Node245129
Ref: Dwg_EvalExpr245440
Ref: Dwg_EvalVariant245881
Ref: Dwg_FIELD_ChildValue246117
Ref: Dwg_FileDepList_Files246278
Ref: Dwg_FormattedTableData246625
Ref: Dwg_FormattedTableMerged246872
Ref: Dwg_GEODATA_meshface247126
Ref: Dwg_GEODATA_meshpt247242
Ref: Dwg_GridFormat247332
Ref: Dwg_HATCH_Color247750
Ref: Dwg_HATCH_ControlPoint247918
Ref: Dwg_HATCH_DefLine248076
Ref: Dwg_HATCH_Path248350
Ref: Dwg_HATCH_PathSeg248808
Ref: Dwg_HATCH_PolylinePath249802
Ref: Dwg_LAYER_entry249954
Ref: Dwg_LEADER_ArrowHead250158
Ref: Dwg_LEADER_BlockLabel250331
Ref: Dwg_LEADER_Break250578
Ref: Dwg_LEADER_Line250730
Ref: Dwg_LEADER_Node251284
Ref: Dwg_LIGHTLIST_light251864
Ref: Dwg_LTYPE_dash252016
Ref: Dwg_LWPOLYLINE_width252464
Ref: Dwg_LinkedData252560
Ref: Dwg_LinkedTableData252666
Ref: Dwg_MATERIAL_color252935
Ref: Dwg_MATERIAL_gentexture253109
Ref: Dwg_MATERIAL_mapper253287
Ref: Dwg_MESH_edge253733
Ref: Dwg_MLEADER_AnnotContext253896
Ref: Dwg_MLEADER_Content_Block254862
Ref: Dwg_MLEADER_Content_MText255155
Ref: Dwg_MLINESTYLE_line256154
Ref: Dwg_MLINE_line256393
Ref: Dwg_MLINE_vertex256639
Ref: Dwg_OCD_Dimension256934
Ref: Dwg_PARTIAL_VIEWING_INDEX_Entry257557
Ref: Dwg_POINTCLOUDCOLORMAP_Ramp257766
Ref: Dwg_POINTCLOUDEX_Croppings257997
Ref: Dwg_POINTCLOUD_Clippings258418
Ref: Dwg_POINTCLOUD_IntensityStyle258710
Ref: Dwg_PROXY_LWPOLYLINE258968
Ref: Dwg_R2004_Header259577
Ref: Dwg_SECTION_geometrysettings260693
Ref: Dwg_SECTION_typesettings261465
Ref: Dwg_SPLINE_control_point261905
Ref: Dwg_SUNSTUDY_Dates262098
Ref: Dwg_SummaryInfo_Property262211
Ref: Dwg_TABLEGEOMETRY_Cell262301
Ref: Dwg_TABLESTYLE_CellStyle262677
Ref: Dwg_TABLESTYLE_border263878
Ref: Dwg_TABLESTYLE_rowstyles264004
Ref: Dwg_TABLE_AttrDef264548
Ref: Dwg_TABLE_BreakHeight264744
Ref: Dwg_TABLE_BreakRow264918
Ref: Dwg_TABLE_Cell265086
Ref: Dwg_TABLE_CustomDataItem266797
Ref: Dwg_TABLE_value267010
Ref: Dwg_TableCell267603
Ref: Dwg_TableCellContent268562
Ref: Dwg_TableCellContent_Attr268968
Ref: Dwg_TableDataColumn269169
Ref: Dwg_TableRow269439
Ref: Dwg_UCS_orthopts269850
Ref: Dwg_VALUEPARAM269997
Ref: Dwg_VALUEPARAM_vars270300
Ref: Dwg_MLEADER_Content270400
Ref: Common Entity fields270530
Ref: Common Object fields272376
Node: Sections272806
Node: HEADER_275138
Node: OBJECTS_275272
Node: CLASSES275603
Node: HANDLES277004
Node: R2004_Header277389
Node: UNKNOWN277681
Node: SummaryInfo277903
Node: Preview278707
Node: VBAProject279072
Node: AppInfo279192
Node: AppInfoHistory279368
Node: FileDepList279506
Node: AcDS279709
Node: RevHistory279910
Node: Security280048
Node: ObjFreeSpace280187
Node: Template280341
Node: AuxHeader280507
Node: Signature280669
Node: INFO280787
Node: SYSTEM_MAP280928
Node: Tables281099
Node: EXTRAS282602
Node: Structures285278
Node: EED285475
Node: XDATA287871
Node: Functions288217
Node: Decoding289677
Node: Encoding292452
Node: add api295016
Node: dynapi299155
Node: strings303901
Node: Other Formats305975
Node: DXF306315
Node: DXFB306997
Node: JSON307299
Node: GeoJSON308336
Node: Errors309362
Node: Programs310702
Ref: dwgread310889
Ref: dwgwrite311461
Ref: dxfwrite311679
Ref: dwg2dxf311908
Ref: dxf2dwg312409
Ref: dwgrewrite312836
Ref: dwglayers313009
Ref: dwggrep313642
Ref: dwgfilter314143
Ref: dwg2SVG314431
Ref: dwg2ps314976
Ref: dwgplot315456
Ref: load_dwg315719
Ref: dwg2svg2315776
Ref: unknown316094
Ref: dwgfuzz316357
Ref: dwgadd317154
Node: Bindings317420
Node: Reference API318052
Node: Reporting bugs318583
Node: GNU Free Documentation License320363
Node: Index345517
Node: General Index345753
Node: Object and Field Index377436

End Tag Table

Local Variables:
coding: utf-8
End:
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,18 @@
This is the file .../info/dir, which contains the
topmost node of the Info hierarchy, called (dir)Top.
The first time you invoke Info you start off looking at this node.

File: dir, Node: Top This is the top of the INFO tree
This (the Directory node) gives a menu of major topics.
Typing "q" exits, "H" lists all Info commands, "d" returns here,
"h" gives a primer for first-timers,
"mEmacs<Return>" visits the Emacs manual, etc.
In Emacs, you can click mouse button 2 on a menu item or cross reference
to select it.
* Menu:
Libraries
* LibreDWG: (LibreDWG). Free implementation of the DWG file format.
@@ -0,0 +1,65 @@
# example dwgadd file. strings are utf8 and must not exceed length 255
#readdwg "example_2000.dwg"
#readdxf "example_2000.dxf"
#readjson "example_2000.json"
#version 2000
HEADER.VIEWSIZE = 1.2345
HEADER.ANGBASE = 90
HEADER.LUPREC = 3
HEADER.MENU = "other"
HEADER.INSBASE = (1 0 0)
HEADER.LIMMIN = (12 9)
point (2 0 0)
text "test" (0.0 1.0 0.0) 8
block "bloko"
attdef 8 0 "prompt" (0.0 1.0 0.0) TAG "default_text"
line (0 1 0) (1 1 0)
endblk
insert (0 1 0) "bloko" 1 1 1 0
attrib 8 0 (0.0 2.0 0.0) TAG "text1"
minsert (0 1 0) "bloko" 1 1 1 0 1 2 1 1
minsert.rotation = 90
polyline_2d 2 ((0 1) (1 1))
polyline_3d 2 ((0 1 0.5) (1 1 -0.5))
lwpolyline 2 ((0 1) (1 1))
arc (0.0 1.0 0.0) 0.5 0.0 3.0
circle (0.0 1.0 0.0) 0.5
polyline_pface 5 3 ((0 0 1) (2 0 1) (2 2 0) (1 2 0.5) (1 1 -0.5)) ((0 1 2 3) (1 2 -3 4) (2 -3 4))
polyline_mesh 3 2 ((0 0 1) (2 0 2) (2 2 1) (1 2 0.5) (1 1 -1) (0 1 0))
dimension_aligned (0 0 0) (2 0 0) (2 2 0)
dimension_ang2ln (0 0 0) (2 0 0) (2 2 0) (2 2 0)
dimension_ang3pt (0 0 0) (2 0 0) (2 1 0) (2 2 0)
dimension_diameter (0 0 0) (2 0 0) 2
dimension_ordinate (0 0 0) (2 0 0) 1
dimension_radius (0 0 0) (2 0 0) 1
dimension_linear (2 0 0) (2 1 0) (0 0 0) 90
3dface (0 0 0) (2 0 0) (2 1 0) (0 2 0)
solid (0 0 0) (2 0) (2 1) (0 2)
trace (0 0 0) (2 0) (2 1) (0 2)
shape "roman.shx" (0 0 0) 2 45
ellipse (0 0 0) 2.0 0.75
spline 3 ((0 0 0) (2 0 0) (2 1 0)) (0 2 0) (0 3 0)
ray (0 0 0) (2 0 0)
xline (0 0 0) (2 0 0)
dictionary "ACAD_MATERIAL" "Global" 0
xrecord dictionary "REFRACTIONTILE"
mtext (0 1 0) 10 "test\ntext"
leader 2 ((0 0 0) (0 1 0)) mtext 15
tolerance "testtekst" (0 0 0) (0 0 1)
mlinestyle "Double"
mlinestyle.start_angle = 45
mline 4 ((3.9 1.5 0) (3.2 1.8 0) (4.6 1.0 0) (3.8 1.7 0))
dimstyle "Dim1"
dimstyle.DIMSCALE = 2.0
dimstyle.DIMUPT = 1
ucs (0 1 0) (1 0 0) (0 0 1) "Ucs1"
ucs.ucs_elevation = 1.0
viewport "Viewport1"
layout viewport "Model" "ANSI_A_(8.50_x_11.00_Inches)"
torus (10 5 0) (0 0 1) 19 2.78
sphere (10 5 0) (0 0 1) 15
cylinder (10 5 0) (0 0 1) 15 5 5 5
cone (10 5 0) (0 0 1) 15 5 5 0
wedge (10 5 0) (0 0 1) 3.3 2.4 4.8
box (10 5 0) (0 0 1) 3.3 2.0 2.5
pyramid (10 5 0) (0 0 1) 4.5 4 2.0 2.5
@@ -0,0 +1,32 @@
# example dwgadd file for r10. strings are utf8 and must not exceed length 255
HEADER.VIEWSIZE = 1.2345
HEADER.ANGBASE = 90
HEADER.LUPREC = 3
HEADER.MENU = "other"
HEADER.INSBASE = (1 0 0)
HEADER.LIMMIN = (12 9)
point (2 0 0)
text "test" (0.0 1.0 0.0) 8
block "BLOKO"
attdef 8 0 "prompt" (0.0 1.0 0.0) TAG "default_text"
line (0 1 0) (1 1 0)
endblk
insert (0 1 0) "BLOKO" 1 1 1 0
attrib 8 0 (0.0 2.0 0.0) TAG "text1"
polyline_2d 2 ((0 1) (1 1))
polyline_3d 2 ((0 1 0) (1 1 0))
arc (0.0 1.0 0.0) 0.5 0.0 3.0
circle (0.0 1.0 0.0) 0.5
polyline_pface 5 3 ((0 0 0) (2 0 0) (2 2 0) (1 2 0) (1 1 0)) ((0 1 2 3) (1 2 3 4) (2 3 4 5))
polyline_mesh 3 2 ((0 0 0) (2 0 0) (2 2 0) (1 2 0) (1 1 0) (0 1 0))
dimension_aligned (0 0 0) (2 0 0) (2 2 0)
dimension_ang2ln (0 0 0) (2 0 0) (2 2 0) (2 2 0)
dimension_ang3pt (0 0 0) (2 0 0) (2 1 0) (2 2 0)
dimension_diameter (0 0 0) (2 0 0) 2
dimension_ordinate (0 0 0) (2 0 0) 1
dimension_radius (0 0 0) (2 0 0) 1
dimension_linear (2 0 0) (2 1 0) (0 0 0) 90
3dface (0 0 0) (2 0 0) (2 1 0) (0 2 0)
solid (0 0 0) (2 0) (2 1) (0 2)
trace (0 0 0) (2 0) (2 1) (0 2)
shape "ROMAN.SHX" (0 0 0) 2 45
@@ -0,0 +1,40 @@
# example dwgadd file for r11. strings are utf8 and must not exceed length 255
HEADER.VIEWSIZE = 1.2345
HEADER.ANGBASE = 90
HEADER.LUPREC = 3
HEADER.MENU = "other"
HEADER.INSBASE = (1 0 0)
HEADER.LIMMIN = (12 9)
point (2 0 0)
text "test" (0.0 1.0 0.0) 8
block "BLOKO"
attdef 8 0 "prompt" (0.0 1.0 0.0) TAG "default_text"
line (0 1 0) (1 1 0)
endblk
insert (0 1 0) "BLOKO" 1 1 1 0
attrib 8 0 (0.0 2.0 0.0) TAG "text1"
polyline_2d 2 ((0 1) (1 1))
polyline_3d 2 ((0 1 0.5) (1 1 -0.5))
arc (0.0 1.0 0.0) 0.5 0.0 3.0
circle (0.0 1.0 0.0) 0.5
polyline_pface 5 3 ((0 0 1) (2 0 1) (2 2 0) (1 2 0.5) (1 1 -0.5)) ((0 1 2 3) (1 2 -3 4) (2 -3 4))
polyline_mesh 3 2 ((0 0 1) (2 0 2) (2 2 1) (1 2 0.5) (1 1 -1) (0 1 0))
dimension_aligned (0 0 0) (2 0 0) (2 2 0)
dimension_ang2ln (0 0 0) (2 0 0) (2 2 0) (2 2 0)
dimension_ang3pt (0 0 0) (2 0 0) (2 1 0) (2 2 0)
dimension_diameter (0 0 0) (2 0 0) 2
dimension_ordinate (0 0 0) (2 0 0) 1
dimension_radius (0 0 0) (2 0 0) 1
dimension_linear (2 0 0) (2 1 0) (0 0 0) 90
3dface (0 0 0) (2 0 0) (2 1 0) (0 2 0)
solid (0 0 0) (2 0) (2 1) (0 2)
trace (0 0 0) (2 0) (2 1) (0 2)
shape "ROMAN.SHX" (0 0 0) 2 45
dimstyle "DIM1"
dimstyle.DIMSCALE = 2.0
dimstyle.DIMUPT = 1
ucs (0 1 0) (1 0 0) (0 0 1) "UCS1"
ucs.ucs_elevation = 1.0
layer "FOO"
line (0 0 0) (100 0 100)
line.layer = "FOO"
@@ -0,0 +1,16 @@
# example dwgadd file for r1.4. strings are utf8 and must not exceed length 255
HEADER.TEXTSIZE = 1.2345
HEADER.LUPREC = 3
HEADER.INSBASE = (1 0)
HEADER.LIMMIN = (12 9)
point (2 0 0)
text "test" (0.0 1.0 0.0) 8
block "BLOKO"
line (0 1 0) (1 1 0)
endblk
insert (0 1 0) "BLOKO" 1 1 1 0
arc (0.0 1.0 0.0) 0.5 0.0 3.0
circle (0.0 1.0 0.0) 0.5
solid (0 0 0) (2 0) (2 1) (0 2)
trace (0 0 0) (2 0) (2 1) (0 2)
shape "ROMAN.SHX" (0 0 0) 2 45
@@ -0,0 +1,23 @@
# example dwgadd file for r2.10. strings are utf8 and must not exceed length 255
HEADER.VIEWSIZE = 1.2345
HEADER.LUPREC = 3
HEADER.MENU = "other"
HEADER.INSBASE = (1 0 0)
HEADER.LIMMIN = (12 9)
point (2 0 0)
text "test" (0.0 1.0 0.0) 8
block "BLOKO"
attdef 8 0 "prompt" (0.0 1.0 0.0) TAG "default_text"
line (0 1 0) (1 1 0)
endblk
insert (0 1 0) "BLOKO" 1 1 1 0
attrib 8 0 (0.0 2.0 0.0) TAG "text1"
polyline_2d 2 ((0 1) (2 3))
polyline_3d 2 ((0 1 2) (3 4 5))
arc (0.0 1.0 0.0) 0.5 0.0 3.0
circle (0.0 1.0 0.0) 0.5
polyline_pface 5 3 ((0 0 0) (2 0 0) (2 2 0) (1 2 0) (1 1 0)) ((0 1 2 3) (1 2 3 4) (2 3 4 5))
polyline_mesh 3 2 ((0 0 0) (2 0 0) (2 2 0) (1 2 0) (1 1 0) (0 1 0))
solid (0 0 0) (2 0) (2 1) (0 2)
trace (0 0 0) (2 0) (2 1) (0 2)
shape "ROMAN.SHX" (0 0 0) 2 45
@@ -0,0 +1,36 @@
#! /usr/bin/env python3
from libredwg import (
Dwg_Data,
dwg_read_file,
new_Dwg_Object_Array,
Dwg_Object_Array_getitem,
)
import sys
if len(sys.argv) != 2:
print("Usage: load_dwg.py <filename>")
exit()
filename = sys.argv[1]
a = Dwg_Data()
a.object = new_Dwg_Object_Array(1000)
error = dwg_read_file(filename, a)
if error > 0: # critical errors
print("Error: ", error)
if error > 127:
exit()
print(".dwg version: %s" % a.header.version)
print("Num objects: %d " % a.num_objects)
# XXX TODO Error: Dwg_Object_LAYER_CONTROL object has no attribute 'tio'
# print "Num layers: %d" % a.layer_control.tio.object.tio.LAYER_CONTROL.num_entries
# XXX ugly, but works
for i in range(0, a.num_objects):
obj = Dwg_Object_Array_getitem(a.object, i)
print(" Supertype: ", obj.supertype)
print(" Type: ", obj.type)
@@ -0,0 +1,38 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWG2SVG "1" "June 2026" "dwg2SVG 0.14" "User Commands"
.SH NAME
dwg2SVG \- manual page for dwg2SVG 0.14
.SH SYNOPSIS
.B dwg2SVG
[\fI\,OPTION\/\fR]... \fI\,DWGFILE >SVGFILE\/\fR
.SH DESCRIPTION
Converts some 2D elements of the DWG to a SVG.
.TP
\fB\-v[0\-9]\fR, \fB\-\-verbose\fR [0\-9]
verbosity
.TP
\fB\-\-mspace\fR
only model\-space, no paper\-space
.TP
\fB\-\-force\-free\fR
force free
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dwg2SVG
is maintained as a Texinfo manual. If the
.B info
and
.B dwg2SVG
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,57 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWG2DXF "1" "June 2026" "dwg2dxf 0.14" "User Commands"
.SH NAME
dwg2dxf \- manual page for dwg2dxf 0.14
.SH SYNOPSIS
.B dwg2dxf
[\fI\,OPTION\/\fR]... \fI\,DWGFILES\/\fR...
.SH DESCRIPTION
Converts DWG files to DXF.
Default DXFFILE: DWGFILE with .dxf extension (.dxb with \fB\-b\fR) in the current directory.
Existing files are not overwritten, unless \fB\-y\fR is given.
.TP
\fB\-v[0\-9]\fR, \fB\-\-verbose\fR [0\-9]
verbosity
.TP
\fB\-\-as\fR rNNNN
save as version
.IP
Valid versions:
.IP
r12, r14, r2000, r2004, r2007, r2010, r2013, r2018
.IP
Experimental versions:
.IP
r9, r10, r11
.TP
\fB\-m\fR, \fB\-\-minimal\fR
only $ACADVER, HANDSEED and ENTITIES
.TP
\fB\-b\fR, \fB\-\-binary\fR
save as binary DXB
.TP
\fB\-y\fR, \fB\-\-overwrite\fR
overwrite existing files
.TP
\fB\-o\fR outfile, \fB\-\-file\fR
optional, only valid with one single DWGFILE
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dwg2dxf
is maintained as a Texinfo manual. If the
.B info
and
.B dwg2dxf
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,52 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWGADD "1" "June 2026" "dwgadd 0.14" "User Commands"
.SH NAME
dwgadd \- manual page for dwgadd 0.14
.SH SYNOPSIS
.B dwgadd
[\fI\,OPTIONS\/\fR] \fI\,-o outfile addfile\/\fR
.SH DESCRIPTION
Create a DWG (or DXF, JSON) by adding entities with instructions from a special dwgadd file.
See `man 5 dwgadd`
.TP
\fB\-\-dxf\fR:
write DXF, not DWG
.HP
\fB\-\-binary\fR: write binary DXF, not DWG
.TP
\fB\-\-json\fR:
write JSON, not DWG
.TP
\fB\-v[0\-9]\fR, \fB\-\-verbose\fR [0\-9]
verbosity
.TP
\fB\-\-as\fR rNNNN
save as version
.IP
Valid versions:
.IP
r1.1, r1.2, r1.4, r2.6, r2.10, r9, r10, r11, r13, r14, r2000 (default), r2004
.IP
Planned versions:
.IP
r2007, r2010, r2013, r2018
.HP
\fB\-o\fR outfile, \fB\-\-file\fR outfile (default: stdout)
.TP
\fB\-\-version\fR
display the version and exit
.TP
\fB\-\-help\fR
display this help and exit
.SH "SEE ALSO"
The full documentation for
.B dwgadd
is maintained as a Texinfo manual. If the
.B info
and
.B dwgadd
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,33 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWGBMP "1" "June 2026" "dwgbmp 0.14" "User Commands"
.SH NAME
dwgbmp \- manual page for dwgbmp 0.14
.SH SYNOPSIS
.B dwgbmp
[\fI\,OPTION\/\fR]... \fI\,DWGFILE \/\fR[\fI\,thumbnailfile\/\fR]
.SH DESCRIPTION
Extract the DWG thumbnail image as BMP, WMF or PNG.
Default thumbnailfile: DWGFILE with the proper extension.
.TP
\fB\-v[0\-9]\fR, \fB\-\-verbose\fR [0\-9]
verbosity
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dwgbmp
is maintained as a Texinfo manual. If the
.B info
and
.B dwgbmp
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,31 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWGFILTER "1" "June 2026" "dwgfilter 0.14" "User Commands"
.SH NAME
dwgfilter \- manual page for dwgfilter 0.14
.SH DESCRIPTION
dwgfilter [OPTIONS...] dwgfile
.PP
Allow custom jq queries on a temporary JSON dump.
.PP
OPTIONS: \fB\-\-help\fR,\-\-version
.TP
\fB\-\-debug\fR
keep the tmp json
.TP
\fB\-i\fR
write back in\-place, with an updating JQ query
.TP
\&...
all other options are passed to jq. See 'man jq'
.SH "SEE ALSO"
The full documentation for
.B dwgfilter
is maintained as a Texinfo manual. If the
.B info
and
.B dwgfilter
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,56 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWGGREP "1" "June 2026" "dwggrep 0.14" "User Commands"
.SH NAME
dwggrep \- manual page for dwggrep 0.14
.SH SYNOPSIS
.B dwggrep
[\fI\,OPTIONS\/\fR]... \fI\,pattern files\/\fR
.SH DESCRIPTION
Search regex pattern in a list of DWGs.
.TP
\fB\-i\fR
Case\-insensitive pattern
.TP
\fB\-x\fR
Extended regex pattern
.TP
\fB\-c\fR, \fB\-\-count\fR
Print only the count of matched elements.
.TP
\fB\-h\fR, \fB\-\-no\-filename\fR
Print no filename.
.TP
\fB\-y\fR, \fB\-\-type\fR NAME
Search only NAME entities or objects.
.TP
\fB\-d\fR, \fB\-\-dxf\fR NUM
Search only DXF group NUM fields.
.TP
\fB\-t\fR, \fB\-\-text\fR
Search only in TEXT\-like entities.
.TP
\fB\-b\fR, \fB\-\-blocks\fR
Search also in all block definitions.
.TP
\fB\-n\fR, \fB\-\-tables\fR
Search only in table names.
.TP
\fB\-\-help\fR
Display this help and exit
.TP
\fB\-\-version\fR
Output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dwggrep
is maintained as a Texinfo manual. If the
.B info
and
.B dwggrep
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,42 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWGLAYERS "1" "June 2026" "dwglayers 0.14" "User Commands"
.SH NAME
dwglayers \- manual page for dwglayers 0.14
.SH SYNOPSIS
.B dwglayers
[\fI\,OPTION\/\fR]... \fI\,DWGFILE\/\fR
.SH DESCRIPTION
Print list of layers.
.TP
\fB\-x\fR, \fB\-\-extnames\fR
prints EXTNAMES (r13\-r14 only)
.IP
i.e. space instead of _
.TP
\fB\-f\fR, \fB\-\-flags\fR
prints also flags:
.IP
3 chars for: f for frozen, + or \- for ON or OFF, l for locked
.TP
\fB\-o\fR, \fB\-\-on\fR
prints only ON layers
.TP
\fB\-h\fR, \fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dwglayers
is maintained as a Texinfo manual. If the
.B info
and
.B dwglayers
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,39 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWGREAD "1" "June 2026" "dwgread 0.14" "User Commands"
.SH NAME
dwgread \- manual page for dwgread 0.14
.SH SYNOPSIS
.B dwgread
[\fI\,OPTION\/\fR]... \fI\,DWGFILE\/\fR
.SH DESCRIPTION
Reads the DWG into some optional output format to stdout or some file,
and prints error, success or verbose internal progress to stderr.
.TP
\fB\-v[0\-9]\fR, \fB\-\-verbose\fR [0\-9]
verbosity
.TP
\fB\-O\fR fmt, \fB\-\-format\fR fmt
fmt: DXF, DXFB, JSON, minJSON, GeoJSON
.TP
\fB\-o\fR outfile
also defines the output fmt. Default: stdout
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dwgread
is maintained as a Texinfo manual. If the
.B info
and
.B dwgread
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,46 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWGREWRITE "1" "June 2026" "dwgrewrite 0.14" "User Commands"
.SH NAME
dwgrewrite \- manual page for dwgrewrite 0.14
.SH SYNOPSIS
.B dwgrewrite
[\fI\,OPTION\/\fR]... \fI\,INFILE \/\fR[\fI\,OUTFILE\/\fR]
.SH DESCRIPTION
Rewrites the DWG as another DWG.
Default OUTFILE: INFILE with <\-rewrite.dwg> appended.
.TP
\fB\-v[0\-9]\fR, \fB\-\-verbose\fR [0\-9]
verbosity
.TP
\fB\-\-as\fR rNNNN
save as version
.IP
Valid versions:
.IP
r1.4, r2.6, r2.10, r9, r10, r11, r13, r14, r2000 (default), r2004
.IP
Planned versions:
.IP
r2007, r2010, r2013, r2018
.HP
\fB\-o\fR dwgfile, \fB\-\-file\fR
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dwgrewrite
is maintained as a Texinfo manual. If the
.B info
and
.B dwgrewrite
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,55 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DWGWRITE "1" "June 2026" "dwgwrite 0.14" "User Commands"
.SH NAME
dwgwrite \- manual page for dwgwrite 0.14
.SH SYNOPSIS
.B dwgwrite
[\fI\,OPTION\/\fR]... [\fI\,-o DWGFILE\/\fR] \fI\,INFILE\/\fR
.SH DESCRIPTION
Writes a DWG file from various input formats. Only r2000 for now.
.TP
\fB\-v[0\-9]\fR, \fB\-\-verbose\fR [0\-9]
verbosity
.TP
\fB\-\-as\fR rNNNN
save as version
.IP
Valid versions:
.IP
r1.1, r1.2, r1.3, r1.4, r2.0, r2.10, r2.21, r2.22, r2.4,
r2.5, r2.6, r9, r10, r11, r13, r14, r2000, r2004,
r2010, r2013, r2018
.IP
Planned versions:
.IP
r2007
.TP
\fB\-I\fR fmt, \fB\-\-format\fR fmt
DXF, DXFB, JSON
.IP
Planned input formats: GeoJSON, YAML, XML/OGR, GPX
.HP
\fB\-o\fR dwgfile, \fB\-\-file\fR
.TP
\fB\-y\fR, \fB\-\-overwrite\fR
overwrite existing files
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dwgwrite
is maintained as a Texinfo manual. If the
.B info
and
.B dwgwrite
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,49 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DXF2DWG "1" "June 2026" "dxf2dwg 0.14" "User Commands"
.SH NAME
dxf2dwg \- manual page for dxf2dwg 0.14
.SH SYNOPSIS
.B dxf2dwg
[\fI\,OPTION\/\fR]... \fI\,DXFFILES \/\fR...
.SH DESCRIPTION
Converts the DXF to a DWG. Accepts ascii and binary DXF.
Default DWGFILE: DXFFILE with .dwg extension in the current directory.
Existing files are not overwritten, unless \fB\-y\fR is given.
Encoding currently does not work for r2007.
.TP
\fB\-v[0\-9]\fR, \fB\-\-verbose\fR [0\-9]
verbosity
.TP
\fB\-\-as\fR rNNNN
save as version
.IP
Valid versions:
.IP
r12, r14, r2000 (default), r2004
.IP
Planned versions:
.IP
r9, r10, r11, r2007, r2010, r2013, r2018
.TP
\fB\-o\fR outfile, \fB\-\-file\fR
optional, only valid with one single DXFFILE
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dxf2dwg
is maintained as a Texinfo manual. If the
.B info
and
.B dxf2dwg
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,54 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.49.3.
.TH DXFWRITE "1" "June 2026" "dxfwrite 0.14" "User Commands"
.SH NAME
dxfwrite \- manual page for dxfwrite 0.14
.SH SYNOPSIS
.B dxfwrite
[\fI\,OPTION\/\fR]... [\fI\,-o DXFFILE\/\fR] \fI\,INFILE\/\fR
.SH DESCRIPTION
Writes a DXF file from various input formats.
.TP
\fB\-v[0\-9]\fR, \fB\-\-verbose\fR [0\-9]
verbosity
.TP
\fB\-\-as\fR rNNNN
save as version
.IP
Valid versions:
.TP
r9, r10, r11, r12, r13, r14, r2000, r2004, r2007,
r2010, r2013, r2018
.TP
\fB\-I\fR fmt, \fB\-\-format\fR fmt
DWG, DXF, DXFB, JSON
.HP
\fB\-o\fR dxffile, \fB\-\-file\fR
.TP
\fB\-m\fR, \fB\-\-minimal\fR
only $ACADVER, HANDSEED and ENTITIES
.TP
\fB\-b\fR, \fB\-\-binary\fR
create a binary DXF
.TP
\fB\-y\fR, \fB\-\-overwrite\fR
overwrite existing files
.TP
\fB\-\-help\fR
display this help and exit
.TP
\fB\-\-version\fR
output version information and exit
.PP
GNU LibreDWG online manual: <https://www.gnu.org/software/libredwg/>
.SH "SEE ALSO"
The full documentation for
.B dxfwrite
is maintained as a Texinfo manual. If the
.B info
and
.B dxfwrite
programs are properly installed at your site, the command
.IP
.B info LibreDWG
.PP
should give you access to the complete manual.
@@ -0,0 +1,264 @@
.\" -*- mode: troff; coding: utf-8 -*-
.\" Automatically generated by Pod::Man 5.01 (Pod::Simple 3.43)
.\"
.\" Standard preamble:
.\" ========================================================================
.de Sp \" Vertical space (when we can't use .PP)
.if t .sp .5v
.if n .sp
..
.de Vb \" Begin verbatim text
.ft CW
.nf
.ne \\$1
..
.de Ve \" End verbatim text
.ft R
.fi
..
.\" \*(C` and \*(C' are quotes in nroff, nothing in troff, for use with C<>.
.ie n \{\
. ds C` ""
. ds C' ""
'br\}
.el\{\
. ds C`
. ds C'
'br\}
.\"
.\" Escape single quotes in literal strings from groff's Unicode transform.
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.\"
.\" If the F register is >0, we'll generate index entries on stderr for
.\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index
.\" entries marked with X<> in POD. Of course, you'll have to process the
.\" output yourself in some meaningful fashion.
.\"
.\" Avoid warning from groff about undefined register 'F'.
.de IX
..
.nr rF 0
.if \n(.g .if rF .nr rF 1
.if (\n(rF:(\n(.g==0)) \{\
. if \nF \{\
. de IX
. tm Index:\\$1\t\\n%\t"\\$2"
..
. if !\nF==2 \{\
. nr % 0
. nr F 2
. \}
. \}
.\}
.rr rF
.\" ========================================================================
.\"
.IX Title "DWGADD 5"
.TH DWGADD 5 2025-03-25 0.14 "User Commands"
.\" For nroff, turn off justification. Always turn off hyphenation; it makes
.\" way too many mistakes in technical documents.
.if n .ad l
.nh
.SH NAME
dwgadd.5 \- Format of the LibreDWG dwgadd example input\-file
.SH DESCRIPTION
.IX Header "DESCRIPTION"
The dwgadd program accepts this input format to add entities and objects
to a DWG file.
.SH COMMANDS
.IX Header "COMMANDS"
commands start at each new line and must begin with a keyword and has optional arguments.
.PP
comments start with # and are ignored.
.PP
entities are created with the arguments as in the VBA object model AddEntity or AddObject methods.
.PP
entities are added to the model space by default, objects to the DWG or to parents objects, as e.g.
with \f(CW\*(C`xrecord dictionary\*(C'\fR.
.IP "\fBreaddwg\fR ""file.dwg""" 4
.IX Item "readdwg ""file.dwg"""
This must be the very first command. Otherwise a new, empty DWG Document is created.
.IP "\fBreaddxf\fR ""file.dxf""" 4
.IX Item "readdxf ""file.dxf"""
This must be the very first command. Otherwise a new, empty DWG Document is created.
.IP "\fBreadjson\fR ""file.json""" 4
.IX Item "readjson ""file.json"""
This must be the very first command. Otherwise a new, empty DWG Document is created.
.IP \fBimperial\fR 4
.IX Item "imperial"
By default a new DWG is created as metric.
There must be maximal one imperial line.
.IP "\fBversion\fR number" 4
.IX Item "version number"
Writes the output file in this version. It accepts numbers only \f(CW\*(C`[0\-9.]+\*(C'\fR,
prefixes it with an "r", and converts this string via \f(CWdwg_version_as("r"version)\fR
to a \f(CW\*(C`Dwg_Version_Type\*(C'\fR. It accepts only valid DWG versions.
There must be maximal one version line.
.IP \fBpspace\fR 4
.IX Item "pspace"
Add further entities to the paper space.
.IP \fBmspace\fR 4
.IX Item "mspace"
Add further entities to the model space again. This is only needed
when a previous \f(CW\*(C`pspace\*(C'\fR command was used. mspace is the default.
.IP "\fBHEADER\fR.\fIfield\fR = value" 4
.IX Item "HEADER.field = value"
Assign "VALUES" to a HEADER field. See the full documentation of valid HEADER fields.
.IP "\fBpoint\fR (3dpoint)" 4
.IX Item "point (3dpoint)"
.PD 0
.IP "\fBline\fR (3dpoint) (3dpoint)" 4
.IX Item "line (3dpoint) (3dpoint)"
.PD
Create a line variable and entity. You may set all other fields via the
\&\f(CW\*(C`line.field = value\*(C'\fR syntax. E.g. \f(CW\*(C`line.layer = "0"\*(C'\fR
.IP "\fBray\fR (3dpoint) (3dpoint)" 4
.IX Item "ray (3dpoint) (3dpoint)"
.PD 0
.IP "\fBxline\fR (3dpoint) (3dpoint)" 4
.IX Item "xline (3dpoint) (3dpoint)"
.IP "\fBtext\fR ""text_value"" (ins_pt) height" 4
.IX Item "text ""text_value"" (ins_pt) height"
.IP "\fBmtext\fR (ins_pt) height ""text_value""" 4
.IX Item "mtext (ins_pt) height ""text_value"""
.IP "\fBleader\fR num_points ((points)...) mtext type" 4
.IX Item "leader num_points ((points)...) mtext type"
.PD
mtext must be the immediate previous command.
.IP "\fBattribute\fR 8 0 ""prompt"" (0.0 1.0 0.0) ""tag"" ""text""" 4
.IX Item "attribute 8 0 ""prompt"" (0.0 1.0 0.0) ""tag"" ""text"""
.PD 0
.IP "\fBattdef\fR 8 0 ""prompt"" (0.0 1.0 0.0) ""tag"" ""default_text""" 4
.IX Item "attdef 8 0 ""prompt"" (0.0 1.0 0.0) ""tag"" ""default_text"""
.IP "\fBblock\fR ""name""" 4
.IX Item "block ""name"""
.PD
Starts a new block.
.IP \fBendblk\fR 4
.IX Item "endblk"
Ends the current block.
.IP "\fBinsert\fR (ins_pt) ""name"" xscale yscale zscale rotation" 4
.IX Item "insert (ins_pt) ""name"" xscale yscale zscale rotation"
.PD 0
.IP "\fBminsert\fR (ins_pt) ""name"" xscale yscale zscale rotation numrows numcols rowspacing colspacing" 4
.IX Item "minsert (ins_pt) ""name"" xscale yscale zscale rotation numrows numcols rowspacing colspacing"
.IP "\fBpolyline\fR_2d numpts ((2dpoints) ...)" 4
.IX Item "polyline_2d numpts ((2dpoints) ...)"
.IP "\fBlwpolyline\fR numpts ((2dpoints) ...)" 4
.IX Item "lwpolyline numpts ((2dpoints) ...)"
.IP "\fBpolyline\fR_3d numpts ((3dpoints) ...)" 4
.IX Item "polyline_3d numpts ((3dpoints) ...)"
.IP "\fBarc\fR (center) radius startangle endangle" 4
.IX Item "arc (center) radius startangle endangle"
.IP "\fBcircle\fR (center) radius" 4
.IX Item "circle (center) radius"
.IP "\fBpolyline\fR_pface 5 3 ((3dpoints)...) ((faces_4ints)...)" 4
.IX Item "polyline_pface 5 3 ((3dpoints)...) ((faces_4ints)...)"
.IP "\fBpolyline\fR_mesh num_m_verts num_n_verts ((3dpoints)...)" 4
.IX Item "polyline_mesh num_m_verts num_n_verts ((3dpoints)...)"
.IP "\fBdimension\fR_aligned (xline1_pt) (xline2_pt) (text_midpt)" 4
.IX Item "dimension_aligned (xline1_pt) (xline2_pt) (text_midpt)"
.IP "\fBdimension\fR_ang2ln (center_pt) (xline1end_pt) (xline2end_pt) (text_midpt)" 4
.IX Item "dimension_ang2ln (center_pt) (xline1end_pt) (xline2end_pt) (text_midpt)"
.IP "\fBdimension\fR_ang3pt (center_pt) (xline1_pt) (xline2_pt) (text_midpt)" 4
.IX Item "dimension_ang3pt (center_pt) (xline1_pt) (xline2_pt) (text_midpt)"
.IP "\fBdimension\fR_diameter (chord_pt) (far_chord_pt) leader_len" 4
.IX Item "dimension_diameter (chord_pt) (far_chord_pt) leader_len"
.IP "\fBdimension\fR_ordinate (feature_location_pt) (leader_endpt) number" 4
.IX Item "dimension_ordinate (feature_location_pt) (leader_endpt) number"
.IP "\fBdimension\fR_radius (center_pt) (chord_pt) leader_len" 4
.IX Item "dimension_radius (center_pt) (chord_pt) leader_len"
.IP "\fBdimension\fR_linear (xline1_pt) (xline2_pt) (def_pt) angle" 4
.IX Item "dimension_linear (xline1_pt) (xline2_pt) (def_pt) angle"
.IP "\fB3dface\fR (pt1) (pt2) (pt3) [(pt4)]" 4
.IX Item "3dface (pt1) (pt2) (pt3) [(pt4)]"
.IP "\fBsolid\fR (pt1) (pt2\-2d) (pt3\-2d) (pt4\-2d)" 4
.IX Item "solid (pt1) (pt2-2d) (pt3-2d) (pt4-2d)"
.IP "\fBtrace\fR (pt1) (pt2\-2d) (pt3\-2d) (pt4\-2d)" 4
.IX Item "trace (pt1) (pt2-2d) (pt3-2d) (pt4-2d)"
.IP "\fBshape\fR ""name"" (3dpoint) scale angle" 4
.IX Item "shape ""name"" (3dpoint) scale angle"
.IP "\fBviewport\fR ""name""" 4
.IX Item "viewport ""name"""
.IP "\fBellipse\fR (center_pt) major_axis axis_ratio" 4
.IX Item "ellipse (center_pt) major_axis axis_ratio"
.IP "\fBspline\fR num_fitpts ((fitpts)...) (begin_tan_vector) (end_tan_vector)" 4
.IX Item "spline num_fitpts ((fitpts)...) (begin_tan_vector) (end_tan_vector)"
.IP "\fBdictionary\fR ""name"" ""key"" handle_ref" 4
.IX Item "dictionary ""name"" ""key"" handle_ref"
.IP "\fBxrecord\fR dictionary ""name""" 4
.IX Item "xrecord dictionary ""name"""
.IP "\fBtolerance\fR ""testtekst"" (0 0 0) (0 0 1)" 4
.IX Item "tolerance ""testtekst"" (0 0 0) (0 0 1)"
.IP "\fBmlinestyle\fR ""name""" 4
.IX Item "mlinestyle ""name"""
.IP "\fBmline\fR num_points ((points)...)" 4
.IX Item "mline num_points ((points)...)"
.IP "\fBlayout\fR viewport ""name"" ""canonical_media_name""" 4
.IX Item "layout viewport ""name"" ""canonical_media_name"""
.IP "\fBtorus\fR (center_pt) (normal) major_radius minor_radius" 4
.IX Item "torus (center_pt) (normal) major_radius minor_radius"
.IP "\fBsphere\fR (center_pt) (normal) radius" 4
.IX Item "sphere (center_pt) (normal) radius"
.IP "\fBcylinder\fR (center_pt) (normal) height major_radius minor_radius x_radius" 4
.IX Item "cylinder (center_pt) (normal) height major_radius minor_radius x_radius"
.IP "\fBcone\fR (center_pt) (normal) height major_radius minor_radius x_radius" 4
.IX Item "cone (center_pt) (normal) height major_radius minor_radius x_radius"
.IP "\fBwedge\fR (center_pt) (normal) length width height" 4
.IX Item "wedge (center_pt) (normal) length width height"
.IP "\fBbox\fR (center_pt) (normal) length width height" 4
.IX Item "box (center_pt) (normal) length width height"
.IP "\fBpyramid\fR (center_pt) (normal) height sides radius topradius" 4
.IX Item "pyramid (center_pt) (normal) height sides radius topradius"
.IP "\fBlayer\fR ""name""" 4
.IX Item "layer ""name"""
.IP "\fBstyle\fR ""name""" 4
.IX Item "style ""name"""
.IP "\fBltype\fR ""name""" 4
.IX Item "ltype ""name"""
.IP "\fBvport\fR ""name""" 4
.IX Item "vport ""name"""
.IP "\fBview\fR ""name""" 4
.IX Item "view ""name"""
.IP "\fBvport\fR ""name""" 4
.IX Item "vport ""name"""
.IP "\fBlayout\fR ""name""" 4
.IX Item "layout ""name"""
.IP "\fBgroup\fR ""name""" 4
.IX Item "group ""name"""
.IP "\fIentity\fR.\fIfield\fR = value" 4
.IX Item "entity.field = value"
.PD
Every field of the just created entity or object can be set. See "VALUES"
and the full documentation of the available entity fields.
.SH VALUES
.IX Header "VALUES"
Acceptable values must be formatted as:
.IP \fBinteger\fR 4
.IX Item "integer"
negative integers are allowed.
\&\f(CW\*(C`0xXXX\*(C'\fR hex numbers are not accepted.
.IP \fBfloat\fR 4
.IX Item "float"
floats may omit the dot.
.IP "\fBangle\fR as float in degrees, not radians." 4
.IX Item "angle as float in degrees, not radians."
.PD 0
.IP "\fBstring\fR in double-quotes" 4
.IX Item "string in double-quotes"
.PD
E.g "string" or "" for the empty string.
.IP "\fBpoint\fR as (float, float, [float])" 4
.IX Item "point as (float, float, [float])"
esp. (float float) for 2dpoint
.Sp
(float float float) for 3dpoint. (0 0 0) is a valid 3dpoint.
.ie n .IP "\fBhandle\fR as ""D.D.XXX""" 4
.el .IP "\fBhandle\fR as \f(CWD.D.XXX\fR" 4
.IX Item "handle as D.D.XXX"
<C[0\-12]> as type "." decimal digits as size "." hexadecimal absolute ref.
For relative handle codes, like 6,8,10,12 do not take the 3rd handle part asis,
but use the absolute ref.
.Sp
E.g. \f(CW5.1.15\fR for HEADER.LTYPE_BYLAYER.