jwwkit 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. jwwkit/__init__.py +14 -0
  2. jwwkit/__main__.py +4 -0
  3. jwwkit/_commands/__init__.py +1 -0
  4. jwwkit/_commands/_integrity.py +55 -0
  5. jwwkit/_commands/check_installation.py +484 -0
  6. jwwkit/_commands/compare_drawings.py +29 -0
  7. jwwkit/_commands/inspect_jwf.py +24 -0
  8. jwwkit/_commands/recognize_planar_tategu.py +20 -0
  9. jwwkit/_commands/summarize_drawing.py +16 -0
  10. jwwkit/_comparison_composites.py +179 -0
  11. jwwkit/_edit_result_core.py +175 -0
  12. jwwkit/_ezjww_adapter.py +12 -0
  13. jwwkit/_geometry_engine.py +204 -0
  14. jwwkit/_geometry_math.py +93 -0
  15. jwwkit/_normalization_inspection.py +83 -0
  16. jwwkit/_replacement_contract.py +161 -0
  17. jwwkit/_resources.py +23 -0
  18. jwwkit/_spatial_distance.py +95 -0
  19. jwwkit/_spatial_query.py +34 -0
  20. jwwkit/_tategu_features.py +201 -0
  21. jwwkit/_tategu_geometry.py +108 -0
  22. jwwkit/alignment.py +85 -0
  23. jwwkit/cad_model.py +5 -0
  24. jwwkit/cli.py +40 -0
  25. jwwkit/drawing_comparison.py +430 -0
  26. jwwkit/edit_result.py +136 -0
  27. jwwkit/geometry_view.py +141 -0
  28. jwwkit/guides/AGENTS.md +47 -0
  29. jwwkit/guides/docs/api_list.md +1944 -0
  30. jwwkit/guides/docs/common_edit_result_coverage.md +40 -0
  31. jwwkit/guides/docs/drawing_comparison.md +79 -0
  32. jwwkit/guides/docs/edit_results.md +59 -0
  33. jwwkit/guides/docs/geometry_view.md +64 -0
  34. jwwkit/guides/docs/jwf_key_catalog.md +244 -0
  35. jwwkit/guides/docs/jwf_readonly.md +76 -0
  36. jwwkit/guides/docs/plan_api_coverage.md +75 -0
  37. jwwkit/guides/docs/read_view.md +89 -0
  38. jwwkit/guides/docs/reading_to_editing.md +100 -0
  39. jwwkit/guides/docs/schema_normalization.md +64 -0
  40. jwwkit/guides/docs/search_queries.md +95 -0
  41. jwwkit/guides/docs/spatial_search.md +77 -0
  42. jwwkit/guides/docs/tategu_recognition.md +40 -0
  43. jwwkit/guides/docs/text_equivalence.md +42 -0
  44. jwwkit/guides/docs/usage_guide.md +604 -0
  45. jwwkit/guides/docs/workflow_settings.md +95 -0
  46. jwwkit/hatch_geometry.py +77 -0
  47. jwwkit/jwf.py +202 -0
  48. jwwkit/legacy_document.py +2 -0
  49. jwwkit/line_document.py +14 -0
  50. jwwkit/model/__init__.py +1 -0
  51. jwwkit/model/base.py +15 -0
  52. jwwkit/model/block_definition.py +32 -0
  53. jwwkit/model/block_names.py +41 -0
  54. jwwkit/model/block_operations.py +154 -0
  55. jwwkit/model/block_reference.py +39 -0
  56. jwwkit/model/circle.py +44 -0
  57. jwwkit/model/circular_solid.py +72 -0
  58. jwwkit/model/common_entities.py +34 -0
  59. jwwkit/model/dimension.py +49 -0
  60. jwwkit/model/display_state.py +46 -0
  61. jwwkit/model/document.py +585 -0
  62. jwwkit/model/editing.py +270 -0
  63. jwwkit/model/group_audit.py +91 -0
  64. jwwkit/model/group_import.py +249 -0
  65. jwwkit/model/line.py +31 -0
  66. jwwkit/model/point.py +41 -0
  67. jwwkit/model/preflight.py +120 -0
  68. jwwkit/model/preset_rigid.py +42 -0
  69. jwwkit/model/query.py +270 -0
  70. jwwkit/model/query_fields.py +189 -0
  71. jwwkit/model/query_regex_worker.py +20 -0
  72. jwwkit/model/query_text.py +116 -0
  73. jwwkit/model/rigid_audit.py +66 -0
  74. jwwkit/model/solid.py +51 -0
  75. jwwkit/model/spatial.py +99 -0
  76. jwwkit/model/summary.py +93 -0
  77. jwwkit/model/text.py +49 -0
  78. jwwkit/model/text_metrics.py +78 -0
  79. jwwkit/model/transformations.py +176 -0
  80. jwwkit/normalization.py +287 -0
  81. jwwkit/read_view.py +298 -0
  82. jwwkit/reader/__init__.py +1 -0
  83. jwwkit/reader/archive.py +299 -0
  84. jwwkit/reader/images.py +48 -0
  85. jwwkit/reader/jws.py +175 -0
  86. jwwkit/reader/jws_placement.py +78 -0
  87. jwwkit/reader/sxf.py +51 -0
  88. jwwkit/reader/template_names.py +22 -0
  89. jwwkit/reader/text_presets.py +23 -0
  90. jwwkit/record_reader.py +2 -0
  91. jwwkit/replacement_workflow.py +39 -0
  92. jwwkit/resources/README.md +7 -0
  93. jwwkit/resources/empty_template.jww +0 -0
  94. jwwkit/resources/halfwidth_kana_composition_v1.json +35 -0
  95. jwwkit/resources/header.json +3 -0
  96. jwwkit/resources/jwf_schema.json +925 -0
  97. jwwkit/resources/jww_workflow.example.toml +14 -0
  98. jwwkit/resources/text_equivalence_v1.json +1779 -0
  99. jwwkit/safe_edit.py +454 -0
  100. jwwkit/spatial_search.py +144 -0
  101. jwwkit/tategu_recognition.py +181 -0
  102. jwwkit/workflow_settings.py +140 -0
  103. jwwkit/writer/__init__.py +1 -0
  104. jwwkit/writer/header.py +104 -0
  105. jwwkit/writer/legacy_document.py +62 -0
  106. jwwkit/writer/mfc.py +110 -0
  107. jwwkit/writer/preserved.py +265 -0
  108. jwwkit/writer/strings.py +19 -0
  109. jwwkit/writer/template_constants.py +9 -0
  110. jwwkit/writer/template_document.py +390 -0
  111. jwwkit-0.2.0.dist-info/METADATA +174 -0
  112. jwwkit-0.2.0.dist-info/RECORD +117 -0
  113. jwwkit-0.2.0.dist-info/WHEEL +5 -0
  114. jwwkit-0.2.0.dist-info/entry_points.txt +2 -0
  115. jwwkit-0.2.0.dist-info/licenses/LICENSE +21 -0
  116. jwwkit-0.2.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +51 -0
  117. jwwkit-0.2.0.dist-info/top_level.txt +1 -0
jwwkit/__init__.py ADDED
@@ -0,0 +1,14 @@
1
+ """Read, inspect and safely edit Jw_cad drawings."""
2
+ from ._resources import guide_paths, template_path
3
+ from .cad_model import CadBlockDefinition, CadDocument, CadEntity, JwsDocument
4
+ from .jwf import JwfDocument, JwfSetting, jwf_setting_catalog
5
+ from .legacy_document import LegacyDocument
6
+ from .line_document import (
7
+ BlockDefinition, BlockReference, Circle, Dimension, JwwDocument, Line, Point, Solid, Text,
8
+ )
9
+
10
+ __all__ = [
11
+ "BlockDefinition", "BlockReference", "CadBlockDefinition", "CadDocument", "CadEntity",
12
+ "Circle", "Dimension", "JwfDocument", "JwfSetting", "JwsDocument", "JwwDocument",
13
+ "LegacyDocument", "Line", "Point", "Solid", "Text", "guide_paths", "jwf_setting_catalog", "template_path",
14
+ ]
jwwkit/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from .cli import main
2
+
3
+ if __name__ == "__main__":
4
+ raise SystemExit(main())
@@ -0,0 +1 @@
1
+ """Command implementations for jwwkit."""
@@ -0,0 +1,55 @@
1
+ """Offline checks for package data and the installed wheel's RECORD."""
2
+ import base64
3
+ import hashlib
4
+ from importlib import metadata
5
+ from pathlib import Path
6
+
7
+ from jwwkit import template_path
8
+
9
+
10
+ _TEMPLATE_SHA256 = "0913d5f6755b7038c11cda8be75c2502511ce85a007f9bcc2f10df049f6eff1e"
11
+ _DRAWINGS = {".jww", ".jws", ".jwc", ".jwk", ".jwf", ".dxf", ".pdf"}
12
+
13
+
14
+ def check_installed_files() -> int:
15
+ template = template_path()
16
+ package_root = template.parent.parent
17
+ if hashlib.sha256(template.read_bytes()).hexdigest() != _TEMPLATE_SHA256:
18
+ raise ValueError("Installed empty template hash mismatch")
19
+
20
+ files = set()
21
+ for path in package_root.rglob("*"):
22
+ if "__pycache__" in path.parts:
23
+ continue
24
+ if path.is_symlink() or getattr(path.stat(), "st_file_attributes", 0) & 0x400:
25
+ raise ValueError(f"Link or reparse point in jwwkit: {path}")
26
+ if not path.is_file():
27
+ continue
28
+ if path.name.casefold() in {"desktop.ini", "thumbs.db"} or path.suffix == ".pyc":
29
+ continue
30
+ if path.name.casefold() == "ai_settings_summary.json":
31
+ raise ValueError(f"Personal JWF summary in jwwkit: {path}")
32
+ if path.suffix.casefold() in _DRAWINGS and path != template:
33
+ raise ValueError(f"Unexpected drawing in jwwkit: {path}")
34
+ files.add(path)
35
+
36
+ distribution = metadata.distribution("jwwkit")
37
+ records = {
38
+ Path(item.locate()): item
39
+ for item in distribution.files or ()
40
+ if item.parts[0] == "jwwkit" and "__pycache__" not in item.parts
41
+ }
42
+ # Editable installations record the path hook and metadata, not package files.
43
+ if records:
44
+ if set(records) != files:
45
+ extra = sorted(str(path.relative_to(package_root)) for path in files - records.keys())
46
+ missing = sorted(str(path.relative_to(package_root)) for path in records.keys() - files)
47
+ raise ValueError(f"Unexpected or missing installed jwwkit files: extra={extra}, missing={missing}")
48
+ for path, record in records.items():
49
+ data = path.read_bytes()
50
+ if record.hash is None or record.size != len(data):
51
+ raise ValueError(f"Installed jwwkit file size or RECORD mismatch: {path.name}")
52
+ digest = hashlib.new(record.hash.mode, data).digest()
53
+ if base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii") != record.hash.value:
54
+ raise ValueError(f"Installed jwwkit file hash mismatch: {path.name}")
55
+ return len(files)
@@ -0,0 +1,484 @@
1
+ """Offline distribution integrity and executable guide checks (synthetic data only)."""
2
+ import contextlib
3
+ import hashlib
4
+ import io
5
+ import json
6
+ import math
7
+ import platform
8
+ from importlib.resources import files
9
+ from pathlib import Path
10
+ import re
11
+ import struct
12
+ import subprocess
13
+ import sys
14
+ import tempfile
15
+ from unittest.mock import patch
16
+
17
+ import jwwkit
18
+ from jwwkit import guide_paths, template_path
19
+ from jwwkit._commands._integrity import check_installed_files
20
+
21
+
22
+ def _run_tool(command, args, cwd):
23
+ return subprocess.run(
24
+ [sys.executable, "-I", "-B", "-X", "utf8", "-m", "jwwkit", command, *args],
25
+ cwd=cwd, capture_output=True, text=True, encoding="utf-8",
26
+ )
27
+
28
+
29
+ def check(*, compare_command, inspect_jwf_command, summarize_command):
30
+ if sys.flags.optimize:
31
+ raise RuntimeError("Run without -O: executable guide assertions must remain enabled")
32
+ count = check_installed_files()
33
+ guides = guide_paths()["usage_guide"].parent
34
+ template_file = template_path()
35
+ from jwwkit.model.query_text import equivalent_text,normalization_options,_kana_composition
36
+ width_options=normalization_options({'width':True},'exact')
37
+ for original,expected in _kana_composition().items():
38
+ assert equivalent_text(original,width_options)==expected
39
+ assert equivalent_text('A ガ パ ヴ ① カ\u3099',width_options)=='A ガ パ ヴ ① カ\u3099'
40
+ from jwwkit.cad_model import CadDocument, JwsDocument
41
+ from jwwkit.line_document import JwwDocument, Line, Text, Circle
42
+ from jwwkit.writer.mfc import records_bytes
43
+ from jwwkit.reader.jws import JWS700_RESERVED
44
+ text = (guides/'usage_guide.md').read_text(encoding='utf-8')
45
+ blocks = re.findall(r'```python\n(.*?)\n```', text, re.S)
46
+ if len(blocks) != 13:
47
+ raise ValueError('Guide code inventory changed; update executable checks')
48
+ ns = {'__name__': 'distribution_guide'}
49
+ results = []
50
+ with tempfile.TemporaryDirectory(prefix='jww-install-check-') as tmp, \
51
+ patch('builtins.input', side_effect=AssertionError('No background keyboard approval')), \
52
+ contextlib.redirect_stdout(io.StringIO()):
53
+ for i, code in enumerate(blocks):
54
+ with contextlib.chdir(tmp):
55
+ exec(compile(code, f'usage_guide.md:block{i+1}', 'exec'), ns)
56
+ folder = Path(tmp)
57
+ source = folder/'input.jww'
58
+ new = JwwDocument()
59
+ new.set_group_scale(0, 50); new.set_group_scale(1, 25)
60
+ new.add_text((10,10), content='置換前')
61
+ new.add_line((2,2),(4,2),layer=1)
62
+ new.add_line((10,2),(12,2),layer=2)
63
+ new.add_line((160,120),(180,120),layer=4,layer_group=1)
64
+ new.save(source)
65
+ original = source.read_bytes()
66
+ cad = CadDocument.open(source)
67
+ if cad.diagnostics or len(cad.entities) != 4:
68
+ raise AssertionError('Synthetic template/reader smoke failure')
69
+ report = ns['read_report'](source,folder/'summary.json')
70
+ assert report['entity_count']==4 and report['texts'][0]['content']=='置換前'
71
+ assert sum(l['count'] for g in report['layer_groups'] for l in g['layers'])==4
72
+ assert cad.convert_point((1000,0),layer_group=0,from_units='real',to_units='paper')==(20.,0.)
73
+ assert cad.convert_point((1000,0),layer_group=1,from_units='real',to_units='paper')==(40.,0.)
74
+ results.append('summary_and_group_units')
75
+ before=ns['save_group_snapshot'](source,1,folder/'group_snapshot.json')
76
+ assert ns['check_group_snapshot'](source,1,folder/'group_snapshot.json')['passed']
77
+ changed=CadDocument.open(source);changed.set_group_name(1,'AUDIT_CHANGED');changed.save(folder/'group_changed.jww')
78
+ assert not ns['check_group_snapshot'](folder/'group_changed.jww',1,folder/'group_snapshot.json')['passed']
79
+ results.append('strict_group_snapshot_persist_and_difference')
80
+ receipt = ns['plan_edit'](source,folder/'edited.jww',folder/'edit.plan.json')
81
+ assert not (folder/'edited.jww').exists() and source.read_bytes()==original
82
+ # Explicit synthetic test approval, never approval for a user's drawing.
83
+ report = ns['run_approved'](receipt['path'], receipt['sha256'])
84
+ assert (report['modified_count'],report['deleted_count'],report['added_count'])==(2,1,0)
85
+ saved = CadDocument.open(folder/'edited.jww')
86
+ assert saved.entities[0].geometry['content']=='置換後'
87
+ assert saved.line_points(1,units='real')==((200.,100.),(300.,100.))
88
+ assert ns['view'](saved.entities[-1])==ns['view'](cad.entities[-1])
89
+ results.append('plan_edit_reload_and_non_target')
90
+ # Build a documented-schema JWS from synthetic LINE records, not any sample.
91
+ header = b'JwsData.'+b'.'*192+struct.pack('<I2d16d',700,0,0,*([50.]*16))+JWS700_RESERVED+struct.pack('<4d',0,0,4,4)
92
+ registry = dict(classes={},next_index=1)
93
+ data = header+records_bytes([Line((0,0),(4,0),layer=3).record(),Line((4,0),(4,4),layer=5,color=4).record()],registry)
94
+ data += records_bytes([],registry)
95
+ figure = folder/'symbol.jws'; figure.write_bytes(data)
96
+ assert JwsDocument.open(figure).to_bytes()==data
97
+ receipt = ns['plan_symbol'](source,figure,folder/'symbols.jww',folder/'symbol.plan.json')
98
+ report = ns['run_approved'](receipt['path'],receipt['sha256'])
99
+ symbols = CadDocument.open(folder/'symbols.jww')
100
+ assert symbols.block_reference_counts()=={'SYMBOL':2} and report['added_count']==2
101
+ assert symbols.entities[-1].geometry['scale_y']<0
102
+ assert symbols.entities[-1].geometry['scale_x']==1.5
103
+ assert figure.read_bytes()==data
104
+ results.append('jws_place_copy_rotate_reflect_scale')
105
+ groupsource = folder/'groups.jww'
106
+ new = JwwDocument();new.set_group_scale(0,50)
107
+ for x in (0,20):
108
+ new.add_line((x,0),(x+4,0),layer=3,color=2,width=2)
109
+ new.add_line((x+4,0),(x+4,4),layer=5,color=4,linetype=2,width=3)
110
+ new.add_line((80,60),(90,60),layer=4);new.save(groupsource)
111
+ before = CadDocument.open(groupsource)
112
+ receipt = ns['plan_groups'](groupsource,folder/'blocks.jww',folder/'groups.plan.json')
113
+ report = ns['run_approved'](receipt['path'],receipt['sha256'])
114
+ saved = CadDocument.open(folder/'blocks.jww')
115
+ assert saved.block_reference_counts()=={'MARK':2} and len(saved.block_definitions)==1
116
+ assert [saved.get_block_layer_priority(i) for i in (0,1)]==[True,False]
117
+ assert ns['view'](saved.entities[-1])==ns['view'](before.entities[-1])
118
+ assert [e.attributes for e in saved.block_definitions[0].entities]==[e.attributes for e in before.entities[:2]]
119
+ results.append('block_reuse_priority_mixed_attributes')
120
+ rejection = ns['report_plan_refusal'](source,folder/'refuse.plan.json',folder/'refused.jww')
121
+ assert rejection['reason']=='Mirrored glyphs cannot be represented by verified text fields'
122
+ assert not rejection['saved'] and source.read_bytes()==original
123
+ results.append('refusal_without_source_change')
124
+ receipt = ns['plan_edit'](source,folder/'stale.jww',folder/'stale.plan.json')
125
+ source.write_bytes(original+b'changed')
126
+ try:
127
+ ns['run_approved'](receipt['path'],receipt['sha256'])
128
+ except ValueError:
129
+ pass
130
+ else:
131
+ raise AssertionError('Changed source was accepted')
132
+ assert not (folder/'stale.jww').exists();source.write_bytes(original)
133
+ results.append('stale_plan_refused')
134
+ receipt = ns['plan_add_geometry'](source,folder/'add_geometry.jww',folder/'add_geometry.plan.json')
135
+ assert not (folder/'add_geometry.jww').exists()
136
+ report = ns['run_approved'](receipt['path'],receipt['sha256'])
137
+ assert report['added_count']==8 and report['modified_count']==0
138
+ added = CadDocument.open(folder/'add_geometry.jww')
139
+ assert [e.entity_type for e in added.entities[-8:]]==['LINE','TEXT','CIRCLE','ARC','POINT','SOLID','CIRCLE_SOLID','CIRCLE_SOLID']
140
+ assert added.entities[-3].attributes['pen_color']==102 and added.entities[-3].attributes['pen_style']==31
141
+ assert added.entities[-2].attributes['pen_style']==105
142
+ assert added.entities[-2].geometry['solid_mode']*added.get_group_scale(0)==50
143
+ filled=added.entities[-1]
144
+ assert (filled.attributes['layer_group'],filled.attributes['layer'])==(1,7)
145
+ assert filled.geometry['center_x']*added.get_group_scale(1)==2600
146
+ assert filled.geometry['radius']*added.get_group_scale(1)==100
147
+ results.append('eight_geometry_constructor_plans')
148
+ incoming=folder/'incoming.jww';incoming.write_bytes(source.read_bytes())
149
+ receipt=ns['plan_import_group'](source,incoming,folder/'imported.jww',folder/'import.plan.json',source_group=0,target_group=3)
150
+ assert not (folder/'imported.jww').exists()
151
+ report=ns['run_approved'](receipt['path'],receipt['sha256'])
152
+ assert report['added_count']==3 and report['modified_count']==0 and report['deleted_count']==0
153
+ imported=CadDocument.open(folder/'imported.jww')
154
+ assert imported.line_points(5,units='real')==CadDocument.open(source).line_points(1,units='real')
155
+ results.append('import_group_dependency_plan_and_real_coordinates')
156
+
157
+ dimsource = folder/'dimension.jww';new = JwwDocument()
158
+ new.add_dimension(Line((0,0),(10,0)),Text((2,1),(5,1),'10'))
159
+ new.add_line((1,2),(3,2),layer_group=1);new.save(dimsource)
160
+ receipt = ns['plan_names_and_dimension'](dimsource,folder/'names.jww',folder/'names.plan.json')
161
+ assert receipt['plan']['schema_version']==2 and len(receipt['plan']['resource_targets'])==3
162
+ report = ns['run_approved'](receipt['path'],receipt['sha256'])
163
+ named = CadDocument.open(folder/'names.jww')
164
+ assert named.entities[0].geometry['text']['content']=='寸法表示'
165
+ assert named.get_group_scale(1)==100 and named.get_layer_name(0,1)=='線'
166
+ assert named.get_group_name(1)=='配置用' and report['changed_group_count']==2
167
+ assert named.line_points(1,units='real')==((100.,200.),(300.,200.))
168
+ results.append('dimension_names_scales_and_resource_review')
169
+ states=ns['read_display_states'](source)
170
+ assert states['group']['state']=='editable'
171
+ receipt=ns['plan_display_states'](source,folder/'states.jww',folder/'states.plan.json')
172
+ assert not (folder/'states.jww').exists()
173
+ report=ns['run_approved'](receipt['path'],receipt['sha256'])
174
+ states=ns['read_display_states'](folder/'states.jww')
175
+ assert states['group']['state']=='display_only' and states['layer']['state']=='hidden'
176
+ assert report['changed_entity_count']==0 and report['changed_group_count']==1
177
+ results.append('display_states_read_plan_approval_reload')
178
+ receipt = ns['plan_definition'](source,folder/'new_definition.jww',folder/'definition.plan.json')
179
+ report = ns['run_approved'](receipt['path'],receipt['sha256'])
180
+ defined = CadDocument.open(folder/'new_definition.jww')
181
+ assert report['added_count']==2 and defined.block_reference_counts()=={'CREATED':2}
182
+ assert defined.get_block_layer_priority(4) and not defined.get_block_layer_priority(5)
183
+ assert defined.block_definitions[0].entities[0].attributes['flag']==0
184
+ assert [e.attributes['layer'] for e in defined.block_definitions[0].entities]==[3,5]
185
+ assert defined.entities[-2].geometry['ref_x']==20 and defined.entities[-1].geometry['ref_x']==80
186
+ receipt = ns['plan_existing_definition'](folder/'new_definition.jww',defined.block_definitions[0].number,
187
+ folder/'more_refs.jww',folder/'more_refs.plan.json')
188
+ report = ns['run_approved'](receipt['path'],receipt['sha256'])
189
+ assert report['added_count']==1 and not report['definitions_changed']
190
+ assert CadDocument.open(folder/'more_refs.jww').block_reference_counts()=={'CREATED':3}
191
+ results.append('new_definition_alias_priority_and_existing_placement')
192
+ receipt=ns['plan_alignment'](source,[1],[[100,100],[200,100]],[[300,400],[300,500]],
193
+ folder/'aligned.jww',folder/'align.plan.json',distance_tolerance=0,angle_tolerance=90,angle_units='deg')
194
+ review=receipt['plan']['resource_targets'][0]['alignment']
195
+ assert review['angle_input_units']=='deg' and review['angle_difference_deg']==90
196
+ report=ns['run_approved'](receipt['path'],receipt['sha256'])
197
+ assert report['modified_count']==1 and not report['groups_changed']
198
+ assert CadDocument.open(folder/'aligned.jww').line_points(1,units='real')==((300.,400.),(300.,500.))
199
+ results.append('two_point_alignment_real_degrees_and_approval')
200
+ # Exercise the newly distributed workflow using ONLY synthetic files.
201
+ building_paths=[]
202
+ for name in ('current_building','new_building'):
203
+ d=CadDocument.open(template_file)
204
+ for g in (1,2,3):d.set_group_scale(g,100)
205
+ d.set_group_scale(8,50)
206
+ d.add_line((0,0),(1000,100 if name=='new_building' else 0),layer_group=1,units='real')
207
+ d.add_line((20,20),(30,30),layer_group=2,units='real')
208
+ for point in ((0,0),(1000,0)):d.add_point(point,layer_group=3,layer=0,units='real',is_temporary=True)
209
+ path=folder/(name+'.jww');d.save(path);building_paths.append(path)
210
+ current,new_building=building_paths;original_buildings=[p.read_bytes() for p in building_paths]
211
+ refs=ns['replacement_reference_points'](current)
212
+ assert [row['index'] for row in refs]==[2,3] and refs[1]['real_mm']==(1000.,0.)
213
+ replacement=ns['plan_building_replacement'](current,new_building,folder/'replacement.jww',folder/'replacement.plan.json',
214
+ archive_group=8,building_group=1,source_reference_indices=[2,3],target_reference_indices=[2,3],
215
+ distance_tolerance=0,angle_tolerance=0,settings_policy='destination')
216
+ assert not (folder/'replacement.jww').exists()
217
+ assert replacement['plan']['actions'][0]['op']=='set_group_scale'
218
+ assert next(row for row in replacement['plan']['resource_targets'] if 'settings' in row)['settings']['settings_policy']=='destination'
219
+ report=ns['run_approved'](replacement['path'],replacement['sha256'])
220
+ assert (report['modified_count'],report['added_count'],report['deleted_count'])==(1,1,0)
221
+ audit=ns['check_replacement_groups'](folder/'replacement.jww',Path(replacement['path']).with_suffix('.groups.json'))
222
+ assert audit['passed'] and audit['archive_count']==1 and audit['archive_state']['state']=='hidden'
223
+ assert CadDocument.open(folder/'replacement.jww').get_group_scale(8)==100
224
+ assert [p.read_bytes() for p in building_paths]==original_buildings
225
+ results.append('building_replacement_destination_policy_and_complete_group_audits')
226
+ for case,angle in (('translation',0.),('rotation',math.radians(5))):
227
+ displaced=CadDocument.open(new_building)
228
+ displaced.transform_entities([0,2,3],translation=(1000,-500),rotation=angle,units='real')
229
+ path=folder/(case+'_source.jww');displaced.save(path);source_bytes=path.read_bytes()
230
+ plan=folder/(case+'.plan.json');out=folder/(case+'.jww')
231
+ receipt=ns['plan_building_replacement'](current,path,out,plan,archive_group=8,building_group=1,
232
+ source_reference_indices=[2,3],target_reference_indices=[2,3],distance_tolerance=.001,
233
+ angle_tolerance=5.00001 if angle else 0.,angle_units='deg',settings_policy='destination')
234
+ assert not out.exists()
235
+ evidence=Path(receipt['path']).with_suffix('.groups.json')
236
+ measured=json.loads(evidence.read_text(encoding='utf-8'))['alignment']
237
+ assert measured['applied_scale']==1 and abs(measured['angle_difference_deg']+math.degrees(angle))<1e-8
238
+ assert 'measured_endpoint_displacement_mm' in measured
239
+ report=ns['run_approved'](receipt['path'],receipt['sha256'])
240
+ assert (report['modified_count'],report['added_count'],report['deleted_count'])==(1,1,0)
241
+ assert ns['check_replacement_groups'](out,evidence)['passed']
242
+ endpoints=CadDocument.open(out).line_points(4,units='real')
243
+ assert all(math.dist(a,b)<1e-6 for a,b in zip(endpoints,((0.,0.),(1000.,100.))))
244
+ assert path.read_bytes()==source_bytes and [p.read_bytes() for p in building_paths]==original_buildings
245
+ results.append('building_replacement_'+case+'_plan_approval_and_exact_saved_attributes')
246
+ rejected=CadDocument.open(new_building);rejected.set_group_scale(1,50)
247
+ different_scale=folder/'different_scale.jww';rejected.save(different_scale)
248
+ for case,path,angular in (('different_scale',different_scale,5.00001),('angle_exceeded',folder/'rotation_source.jww',4.9)):
249
+ plan=folder/(case+'_denied.plan.json');out=folder/(case+'_denied.jww')
250
+ try:
251
+ ns['plan_building_replacement'](current,path,out,plan,archive_group=8,building_group=1,
252
+ source_reference_indices=[2,3],target_reference_indices=[2,3],distance_tolerance=.001,
253
+ angle_tolerance=angular,angle_units='deg',settings_policy='destination')
254
+ except ValueError:pass
255
+ else:raise AssertionError('Unsupported scale/tolerance accepted')
256
+ assert not plan.exists() and not out.exists() and not plan.with_suffix('.groups.json').exists()
257
+ results.append('building_replacement_unequal_scale_and_angle_tolerance_refused')
258
+ cleanup=ns['plan_archive_cleanup'](folder/'replacement.jww',folder/'after_review.jww',folder/'cleanup.plan.json',archive_group=8)
259
+ assert not (folder/'after_review.jww').exists() and len(cleanup['plan']['targets'])==1
260
+ assert cleanup['sha256']!=replacement['sha256']
261
+ report=ns['run_approved'](cleanup['path'],cleanup['sha256'])
262
+ assert (report['deleted_count'],report['added_count'],report['modified_count'])==(1,0,0)
263
+ before=CadDocument.open(folder/'replacement.jww');after=CadDocument.open(folder/'after_review.jww')
264
+ assert [ns['view'](e) for e in before.entities if e.attributes['layer_group']!=8]==[ns['view'](e) for e in after.entities]
265
+ results.append('separate_archive_cleanup_plan_after_synthetic_review')
266
+ sx=ns['read_sxf_report'](source)
267
+ assert sx['colors']==cad.get_sxf_colors() and sx['linetypes']==cad.get_sxf_linetypes()
268
+ assert cad.get_sxf_color('red')['code']==102
269
+ results.append('sxf_named_definition_reading')
270
+ # Execute the actual supplemental documentation on synthetic inputs.
271
+ from jwwkit.drawing_comparison import compare_drawings,write_comparison
272
+ from jwwkit.jwf import JwfDocument
273
+ supplemental={}
274
+ for filename,expected_blocks in [('drawing_comparison.md',1),('jwf_readonly.md',1),('search_queries.md',1)]:
275
+ codes=re.findall(r'```python\n(.*?)\n```',(guides/filename).read_text(encoding='utf-8'),re.S)
276
+ assert len(codes)==expected_blocks
277
+ supplemental[filename]=len(codes)
278
+ if filename=='drawing_comparison.md':
279
+ (folder/'before.jww').write_bytes(source.read_bytes())
280
+ (folder/'after.jww').write_bytes((folder/'edited.jww').read_bytes())
281
+ elif filename=='jwf_readonly.md':
282
+ (folder/'settings.jwf').write_bytes(b'PCOLLOR_1=255 0 0 10 .4\r\n')
283
+ (folder/'other.jwf').write_bytes(b'PCOLLOR_1=0 0 0 10 .4\r\n')
284
+ (folder/'drawing.jww').write_bytes(source.read_bytes())
285
+ example={}
286
+ with contextlib.chdir(folder):
287
+ for code in codes:exec(compile(code,filename,'exec'),example)
288
+ if filename=='drawing_comparison.md':
289
+ assert example['result']['complete'] and (folder/'comparison.json').is_file()
290
+ assert example['result']['element_accounting']['left_selected']==3
291
+ elif filename=='jwf_readonly.md':
292
+ assert example['red']==255 and example['changes']['rows'][0]['status']=='different'
293
+ assert example['stored']['rows'][0]['status']=='not_comparable'
294
+ else:
295
+ from jwwkit.safe_edit import execute_plan
296
+ querydoc=CadDocument.open(template_file);querydoc.set_group_scale(0,100)
297
+ querydoc.add_line((0,0),(20,0),layer=3)
298
+ querydoc.add_arc((0,0),10,0,2,layer=5)
299
+ querydoc.add_text((0,0),content='AW-01')
300
+ querysource=folder/'query_input.jww';querydoc.save(querysource)
301
+ assert example['inspect_drawing'](querysource)['long_shapes']==(0,1)
302
+ assert example['inspect_drawing'](querysource)['aw_texts']==(2,)
303
+ receipt=example['plan_search_move'](querysource,folder/'query_output.jww',folder/'query_review.json')
304
+ assert receipt['plan']['schema_version']==3
305
+ execution=execute_plan(receipt['path'],approved_sha256=receipt['sha256'])
306
+ assert execution['modified_count']==2 and execution['saved_all_fields_match']
307
+ assert querydoc.search_entities({'field':'text','match':'regex','value':'^AW-'})['match_count']==1
308
+ results.append('json_query_documented_plan_and_isolated_regex')
309
+ scoped=compare_drawings(current,folder/'replacement.jww',left_scope={'groups':[1]},right_scope={'groups':[1]})
310
+ assert scoped['complete'] and (scoped['summary']['added'],scoped['summary']['deleted'])==(1,1)
311
+ execution=json.loads((folder/'replacement.changes.json').read_text(encoding='utf-8'))
312
+ assert (execution['modified_count'],execution['added_count'],execution['deleted_count'])==(1,1,0)
313
+ assert execution['saved_all_fields_match'] and execution['approved_plan_sha256']==replacement['sha256']
314
+ results.append('independent_comparison_docs_group_scope_versus_execution_record')
315
+ compound=CadDocument.open(dimsource)
316
+ compound.add_solid(((20,0),(30,10),(30,0),(20,10)))
317
+ compound.add_circle_solid((40,0),5)
318
+ definition=compound.add_block_definition('CHECK',[Line((0,0),(10,0))])
319
+ compound.place_block(definition.number,(60,0))
320
+ compound_path=folder/'compound.jww';compound.save(compound_path)
321
+ compound_bytes=compound_path.read_bytes();r=compare_drawings(compound_path,compound_path)
322
+ assert r['complete'] and r['summary']['unchanged']==len(compound.entities)
323
+ assert {x['type'] for x in r['left_entities']}>={'DIMENSION','BLOCK','SOLID','CIRCLE_SOLID'}
324
+ assert compound_path.read_bytes()==compound_bytes and r['definitions']['matches'][0]['semantic_equal']
325
+ results.append('compound_comparison_dimensions_blocks_solids')
326
+ # Fully synthetic compatibility fixtures: no manufacturer DXF/JWW is packaged.
327
+ from jwwkit.reader.archive import read_records_bytes,RecordError
328
+ from jwwkit.writer.preserved import serialize_body
329
+ template=(template_file).read_bytes()
330
+ end=read_records_bytes(template)['header_end']
331
+ for version in (600,700):
332
+ header=bytearray(template[:end]);struct.pack_into('<I',header,8,version)
333
+ records=[Line((0,0),(10,0)).record(),Text((0,0),(5,0),'synthetic').record(),
334
+ Circle((5,5),2).record(),Circle((10,10),3,is_full_circle=False,arc_angle=1).record()]
335
+ raw=bytes(header)+serialize_body(records,[],version)
336
+ declarations=read_records_bytes(raw)['class_declarations']
337
+ fixture=bytearray(raw)
338
+ for declaration in declarations:struct.pack_into('<H',fixture,declaration['offset']+2,0)
339
+ source0=folder/f'schema0_{version}.jww';source0.write_bytes(fixture)
340
+ try:CadDocument.open(source0)
341
+ except RecordError as exc:assert 'schema_profile="rebro_schema0"' in str(exc)
342
+ else:raise AssertionError('Default reader accepted schema0')
343
+ readonly=CadDocument.open(source0,schema_profile='rebro_schema0')
344
+ target0=folder/f'schema0_{version}_copy.jww';readonly.save(target0)
345
+ again=CadDocument.open(target0,schema_profile='rebro_schema0')
346
+ assert target0.read_bytes()==source0.read_bytes()
347
+ assert again.entities==readonly.entities
348
+ assert again._source_records['class_declarations']==readonly._source_records['class_declarations']
349
+ try:readonly.add_line((0,0),(1,0))
350
+ except ValueError as exc:assert 'read-only' in str(exc)
351
+ else:raise AssertionError('Schema0 editing was accepted')
352
+ results.append('explicit_schema0_readonly_file_roundtrip_v600_v700')
353
+ results.append('jwf_documentation_read_diff_no_settings_application')
354
+ verified=folder/'verified.jwf'
355
+ widths=' '.join(str(cad.get_text_preset(i)['size_x']) for i in range(1,11))
356
+ palette=cad.header['palette']['pen_colors'][1]
357
+ verified.write_bytes(('MWIDE='+widths+'\nLCOLLOR_1='+f'{palette&255} {(palette>>8)&255} {(palette>>16)&255} 1\nKEY_A=5 1\n').encode('cp932'))
358
+ declaration=JwfDocument.open(verified);compared=declaration.compare_drawing(cad)
359
+ assert sum(x['status']=='same' for x in compared['rows'])==13
360
+ assert next(x for x in compared['rows'] if x['key']=='KEY_A')['status']=='not_comparable'
361
+ assert cad._source_bytes==original and not any(hasattr(declaration,n) for n in ('save','write','apply'))
362
+ results.append('jwf_proven_drawing_fields_only')
363
+ for tool,args,output in [(compare_command,['--left',str(current),'--right',str(folder/'replacement.jww'),'--left-groups','1','--right-groups','1'],'comparison_cli.json'),
364
+ (inspect_jwf_command,['--source',str(folder/'settings.jwf'),'--other',str(folder/'other.jwf'),'--drawing',str(source)],'jwf_cli.json')]:
365
+ proc=_run_tool(tool, [*args, '--output', str(folder/output)], folder)
366
+ assert proc.returncode==0,proc.stderr
367
+ assert (folder/output).is_file()
368
+ results.append('standalone_comparison_and_jwf_clis')
369
+ cli = _run_tool(summarize_command, ['--source', str(source), '--output', str(folder/'cli.json')], folder)
370
+ assert cli.returncode==0,cli.stderr
371
+ assert json.loads((folder/'cli.json').read_text(encoding='utf-8'))['entity_count']==4
372
+ assert source.read_bytes()==original
373
+ # Execute newer guides without relying on an original checkout or CAD.
374
+ new_guides={};workflow_checks=[]
375
+ for filename in ('read_view.md','geometry_view.md','spatial_search.md','workflow_settings.md','text_equivalence.md','edit_results.md','schema_normalization.md','reading_to_editing.md'):
376
+ codes=re.findall(r'```python\n(.*?)\n```',(guides/filename).read_text(encoding='utf-8'),re.S)
377
+ assert len(codes)==1;new_guides[filename]=len(codes);new_ns={}
378
+ with contextlib.chdir(folder):
379
+ for code in codes:exec(compile(code,filename,'exec'),new_ns)
380
+ if filename=='workflow_settings.md':workflow_ns=new_ns
381
+ if filename=='text_equivalence.md':equivalence_ns=new_ns
382
+ if filename=='edit_results.md':edit_ns=new_ns
383
+ if filename=='schema_normalization.md':normalization_ns=new_ns
384
+ if filename=='reading_to_editing.md':pipeline_ns=new_ns
385
+ policy_folder=folder/'job';policy_folder.mkdir();config=policy_folder/'jww_workflow.toml'
386
+ config.write_text('schema_version=1\n[building_replacement]\narchive_group="first_empty"\n',encoding='utf-8')
387
+ assert workflow_ns['read_policy'](policy_folder)['effective']['archive_group']=='first_empty'
388
+ kw=dict(work_dir=policy_folder,building_group=1,source_reference_indices=[2,3],target_reference_indices=[2,3],distance_tolerance=0,angle_tolerance=0,settings_policy='destination')
389
+ rec=workflow_ns['plan_replacement'](current,new_building,folder/'wf_archive.jww',folder/'wf_archive.json',**kw)
390
+ assert rec['plan']['schema_version']==4 and rec['plan']['workflow']['context']['archive_group']==4
391
+ config.write_text('schema_version=1\nprofile="quick"\n',encoding='utf-8')
392
+ report=workflow_ns['run_replacement_approved'](rec['path'],rec['sha256'])
393
+ assert report['workflow_audit']['passed'] and report['deleted_count']==0
394
+ workflow_checks.append('first_empty_fixed_and_changed_config_ignored')
395
+ rec=workflow_ns['plan_replacement'](current,new_building,folder/'wf_delete.jww',folder/'wf_delete.json',**kw)
396
+ config.unlink();report=workflow_ns['run_replacement_approved'](rec['path'],rec['sha256'])
397
+ assert report['workflow_audit']['passed'] and report['deleted_count']==1 and report['added_count']==1
398
+ assert report['workflow']['settings']['effective']['old_group_action']=='delete'
399
+ workflow_checks.append('quick_deletion_approved_plan_and_saved_group_audit')
400
+ config.write_text('schema_version=1\n[open]\nnormalize=true\n',encoding='utf-8')
401
+ try:workflow_ns['read_policy'](policy_folder)
402
+ except ValueError:pass
403
+ else:raise AssertionError('Future opening setting must be rejected')
404
+ assert len(CadDocument.open(current).entities)==4
405
+ assert [p.read_bytes()for p in building_paths]==original_buildings and source.read_bytes()==original
406
+ workflow_checks.append('unknown_open_settings_refused_no_implicit_read_originals_unchanged')
407
+ assert equivalence_ns['find_aw'](source)['complete']
408
+ common_plan=edit_ns['prepare_move'](source,folder/'common.jww',folder/'common.plan.json',[0],[10,0])
409
+ assert common_plan['status']=='planned'
410
+ common_preview=edit_ns['inspect_plan'](common_plan['plan']['path'],common_plan['plan']['sha256'])
411
+ assert common_preview['projected_counts']['modified']==1
412
+ common_saved=edit_ns['run_approved'](common_plan['plan']['path'],common_plan['plan']['sha256'])
413
+ assert common_saved['phase']=='verified'and common_saved['counts']['modified']==1 and source.read_bytes()==original
414
+ # Synthetic schema0 only; never normalize a user/manufacturer drawing here.
415
+ from jwwkit.reader.archive import read_records_bytes
416
+ from jwwkit.writer.preserved import serialize_body
417
+ normalization_checks=[]
418
+ config.write_text('schema_version=2\n[opening]\nrebro_schema0="normalize"\n',encoding='utf-8')
419
+ raw=source.read_bytes();header=raw[:read_records_bytes(raw)['header_end']]
420
+ normal=header+serialize_body([Line((0,0),(1,1)).record(),Text((0,0),(5,0),'合成').record(),Circle((2,2),3).record()],[],700,encoding='utf-16le')
421
+ data=bytearray(normal)
422
+ for declaration in read_records_bytes(normal)['class_declarations']:
423
+ struct.pack_into('<H',data,declaration['offset']+2,0)
424
+ schema0=folder/'schema0.jww';schema0.write_bytes(data)
425
+ standard=folder/'standard.jww';standard.write_bytes(normal)
426
+ planned=normalization_ns['prepare_normalization'](policy_folder,[schema0,standard],folder/'normalized',folder/'normalize.plan.json')
427
+ assert planned['plan']['schema_version']==6 and [e['status']for e in planned['plan']['entries']]==['normalize','not_needed']
428
+ config.write_text('schema_version=2\n[opening]\nrebro_schema0="reject"\n',encoding='utf-8')
429
+ if platform.system() == 'Windows':
430
+ completed=normalization_ns['run_normalization_approved'](planned['plan_path'],planned['plan_sha256'])
431
+ normalized=Path(completed['output_dir'])/completed['entries'][0]['output_name']
432
+ assert completed['verified'] and normalized.read_bytes()==normal and schema0.read_bytes()==bytes(data)
433
+ assert len(list(normalized.parent.glob('*.jww')))==1
434
+ normalization_checks.append('synthetic_schema_only_mixed_noop_frozen_settings')
435
+ edit=normalization_ns['prepare_separate_edit'](completed['result_path'],normalized,folder/'separate_edit.jww',folder/'separate_edit.plan.json',[0],[1,0])
436
+ assert edit['plan']['schema_version']in (1,2,3,4,5) and len(edit['plan']['dependencies'])>=4
437
+ assert not (folder/'separate_edit.jww').exists()
438
+ normalization_checks.append('separate_edit_plan_only_with_provenance_dependencies')
439
+ else:
440
+ normalization_checks.append('正規化の一括確定と確定結果に基づく編集計画: 対象外(Windows専用)')
441
+ # One synthetic workflow connects views, shape/point queries, U4 and U5.
442
+ pipeline_checks=[]
443
+ pipe=CadDocument.open(template_file)
444
+ pipe.set_group_scale(0,50);pipe.set_group_scale(1,25)
445
+ pipe.add_text((0,0),content='AW-01',units='real')
446
+ pipe.add_text((200,0),content='aw ‐02',units='real')
447
+ pipe.add_text((0,0),content='OTHER',layer_group=1,units='real')
448
+ pipe.add_line((0,100),(100,100),units='real')
449
+ pipe_source=folder/'pipeline.jww';pipe.save(pipe_source);pipe_before=pipe_source.read_bytes()
450
+ inspection=pipeline_ns['inspect_local'](pipe_source,[0,0],group=0,tie_tolerance=0,detail_limit=1)
451
+ assert inspection['text_match_count']==2 and inspection['text_search_complete']
452
+ assert inspection['details']['truncated'] and inspection['closest']['complete']
453
+ assert pipe_source.read_bytes()==pipe_before
454
+ pipeline_checks.append('views_geometry_text_frame_and_scoped_nearest_read_only')
455
+ planned=pipeline_ns['prepare_change'](pipe_source,folder/'pipeline_edited.jww',folder/'pipeline.plan.json',group=0,delta_real_mm=[10,0])
456
+ assert planned['status']=='planned'and planned['projected_counts']['modified']==2
457
+ plan_value=json.loads(Path(planned['plan']['path']).read_text(encoding='utf-8'))
458
+ assert plan_value['schema_version']==5 and plan_value['selection']is not None
459
+ assert not (folder/'pipeline_edited.jww').exists()
460
+ pipeline_checks.append('explicit_equivalence_query_fixed_in_schema5_plan')
461
+ saved=pipeline_ns['run_approved_change'](planned['plan']['path'],planned['plan']['sha256'])
462
+ assert saved['phase']=='verified'and saved['counts']['modified']==2 and saved['counts']['unchanged']==2
463
+ after=CadDocument.open(folder/'pipeline_edited.jww');before=CadDocument.open(pipe_source)
464
+ assert [e.geometry['content']for e in after.entities[:2]]==['AW-01','aw ‐02']
465
+ assert [e.raw for e in before.entities[2:]]==[e.raw for e in after.entities[2:]]
466
+ assert pipe_source.read_bytes()==pipe_before
467
+ pipeline_checks.append('approved_common_result_reload_original_text_and_non_targets_unchanged')
468
+ example=folder/'example_job';example.mkdir()
469
+ (example/'jww_workflow.toml').write_bytes(files('jwwkit').joinpath('resources', 'jww_workflow.example.toml').read_bytes())
470
+ from jwwkit.workflow_settings import load_workflow_settings,resolve_opening_settings
471
+ explicit=resolve_opening_settings(load_workflow_settings(example))
472
+ assert explicit['config_snapshot']['parsed']['schema_version']==2 and explicit['effective']['rebro_schema0']=='reject'
473
+ pipeline_checks.append('generic_settings_v2_example_defaults_to_reject')
474
+ # Loaded project modules must come from this distribution, never another checkout.
475
+ package_root = Path(jwwkit.__file__).resolve().parent
476
+ for name, module in tuple(sys.modules.items()):
477
+ path = getattr(module, "__file__", None)
478
+ if name.startswith("jwwkit.") and path:
479
+ assert Path(path).resolve().is_relative_to(package_root), path
480
+ results.append('standalone_summary_cli_and_module_origins')
481
+ return {'passed': True, 'python': platform.python_version(), 'guide_blocks_executed': len(blocks),
482
+ 'supplemental_guide_blocks_executed': supplemental,
483
+ 'new_guide_blocks_executed':new_guides,'workflow_checks':workflow_checks,'normalization_checks':normalization_checks,'pipeline_checks':pipeline_checks,'common_result_checks':['explicit_equivalence_query','schema5_preview_and_verified_common_result'],
484
+ 'checks': results, 'package_files_checked': count, 'gui_executed': False, 'business_data_used': False}
@@ -0,0 +1,29 @@
1
+ """Read-only independent JWW comparison; incomplete results exit with code 2."""
2
+ from pathlib import Path
3
+ import json
4
+ from jwwkit.drawing_comparison import compare_drawings,write_comparison
5
+
6
+ def add_arguments(p):
7
+ p.add_argument('--left',required=True,type=Path);p.add_argument('--right',required=True,type=Path)
8
+ p.add_argument('--output',required=True,type=Path)
9
+ p.add_argument('--left-groups',nargs='+',type=int);p.add_argument('--right-groups',nargs='+',type=int)
10
+ p.add_argument('--units',choices=('real','paper'),default='real')
11
+ p.add_argument('--position-tolerance',type=float,default=0)
12
+ p.add_argument('--angle-tolerance-degrees',type=float,default=0)
13
+ p.add_argument('--text-size-tolerance',type=float,default=0)
14
+ p.add_argument('--right-rotation-degrees',type=float,default=0)
15
+ p.add_argument('--right-translation',nargs=2,type=float,default=(0,0))
16
+ p.add_argument('--no-line-coverage',action='store_true')
17
+
18
+
19
+ def run(a):
20
+ if a.output.exists():raise FileExistsError(a.output)
21
+ if a.output.suffix.lower()!='.json':raise ValueError('Output must be a new .json')
22
+ r=compare_drawings(a.left,a.right,left_scope={'groups':a.left_groups} if a.left_groups is not None else None,
23
+ right_scope={'groups':a.right_groups} if a.right_groups is not None else None,units=a.units,
24
+ position_tolerance=a.position_tolerance,angle_tolerance_degrees=a.angle_tolerance_degrees,
25
+ text_size_tolerance=a.text_size_tolerance,right_transform=dict(rotation_degrees=a.right_rotation_degrees,translation=a.right_translation),
26
+ line_coverage=not a.no_line_coverage)
27
+ write_comparison(r,a.output)
28
+ print(json.dumps(dict(complete=r['complete'],summary=r['summary']),ensure_ascii=False))
29
+ return 0 if r['complete'] else 2
@@ -0,0 +1,24 @@
1
+ """Local read-only JWF summary/differences; writes only a new diagnostic JSON."""
2
+ from pathlib import Path
3
+ import json
4
+ from jwwkit.jwf import JwfDocument
5
+
6
+
7
+ def add_arguments(p):
8
+ p.add_argument('--source',required=True,type=Path);p.add_argument('--other',type=Path);p.add_argument('--drawing',type=Path)
9
+ p.add_argument('--encoding',default='cp932');p.add_argument('--preserve-invalid',action='store_true')
10
+ p.add_argument('--include-after-end',action='store_true',help='Compare declarations after END as documentation; never apply them')
11
+ p.add_argument('--output',required=True,type=Path)
12
+
13
+
14
+ def run(a):
15
+ if a.output.exists():raise FileExistsError(a.output)
16
+ options=dict(encoding=a.encoding,errors='preserve' if a.preserve_invalid else 'strict')
17
+ doc=JwfDocument.open(a.source,**options);report=dict(jwf=doc.summary())
18
+ if a.other:report['jwf_difference']=doc.diff(JwfDocument.open(a.other,**options),include_inactive=a.include_after_end)
19
+ if a.drawing:
20
+ from jwwkit.cad_model import CadDocument
21
+ report['drawing_comparison']=doc.compare_drawing(CadDocument.open(a.drawing))
22
+ # Surrogate escapes are recorded, not replaced or transmitted.
23
+ with a.output.open('x',encoding='utf-8') as f:json.dump(report,f,ensure_ascii=True,indent=2);f.write('\n')
24
+ print(json.dumps(dict(report=str(a.output),active_keys=len(report['jwf']['active_keys']),diagnostics=len(doc.diagnostics),settings_applied=False),ensure_ascii=True))