12
12
from cadquery .occ_impl .assembly import toJSON
13
13
from cadquery .occ_impl .jupyter_tools import DEFAULT_COLOR
14
14
from docutils .parsers .rst import directives
15
+ from sphinx .application import Sphinx
15
16
16
17
from cadquery .cq_directive import cq_directive_vtk , template_vtk , rendering_code
17
18
45
46
46
47
"""
47
48
49
+ theapp = None
50
+
48
51
49
52
class cadscript_directive (cq_directive_vtk ):
50
53
@@ -90,7 +93,7 @@ def run(self):
90
93
pre_script , script , text = self .get_source_file (content , options .get ("steps" , None ), "text_from_comment" in options )
91
94
else :
92
95
# else use inline code
93
- script = "\n " .join (script )
96
+ script = "\n " .join (script )
94
97
95
98
# add the prefix and postfix
96
99
plot_code = prefix + "\n " + pre_script + "\n " + script + "\n " + postfix .format (result_var = result_var )
@@ -106,7 +109,7 @@ def run(self):
106
109
def get_source_file (self , content , steps , text_from_comment ):
107
110
108
111
content = re .sub (r'^cadscript.show\(.*$' , '' , content , flags = re .MULTILINE ) # remove show() calls
109
- text = ""
112
+ text = []
110
113
pre_script = []
111
114
content = content + "\n " # fix problem with last line
112
115
ellipsis = False
@@ -134,13 +137,15 @@ def get_source_file(self, content, steps, text_from_comment):
134
137
if text_from_comment :
135
138
# extract the text from the comment
136
139
newscript = []
137
- last_comment = ""
140
+ last_comment = []
138
141
for part in script :
139
- last_comment = ""
142
+ last_comment = []
140
143
new_part = ""
141
144
for line in part .split ("\n " )[:- 1 ]: # skip last empty item with [:-1]
142
- if line .startswith ("#" ):
143
- last_comment += line [1 :].strip () + " "
145
+ if line .startswith ("# " ):
146
+ last_comment .append (line [2 :])
147
+ elif line .startswith ("#" ):
148
+ last_comment .append (line [1 :])
144
149
else :
145
150
new_part += line + "\n "
146
151
newscript .append (new_part )
@@ -160,23 +165,24 @@ def generate_output(self, plot_code, text, script, options):
160
165
lines = []
161
166
162
167
if len (text ):
163
- lines .extend (["" , text , "" ])
164
-
165
- if "side-by-side" in options :
166
- lines .append (".. raw:: html" )
167
168
lines .append ("" )
168
- lines .append ( " <div class= \" side-by-side \" ><div class= \" leftside \" >" )
169
+ lines .extend ( text )
169
170
lines .append ("" )
170
171
171
- lines .extend (["" , "::" , "" ])
172
- lines .extend ([" %s" % row .rstrip () for row in script .split ("\n " )])
172
+ lines .append (".. raw:: html" )
173
173
lines .append ("" )
174
-
175
174
if "side-by-side" in options :
176
- lines .append (".. raw:: html" )
177
- lines .append ("" )
178
- lines .append (" </div>" )
179
- lines .append ("" )
175
+ lines .append (" <div class=\" cq-side-by-side\" ><div class=\" leftside\" >" )
176
+ else :
177
+ lines .append (" <div class=\" cq-top-bottom\" ><div class=\" cq-top\" >" )
178
+ lines .append ("" )
179
+
180
+ lines .extend (self .generate_code_block (script ))
181
+
182
+ lines .append (".. raw:: html" )
183
+ lines .append ("" )
184
+ lines .append (" </div>" )
185
+ lines .append ("" )
180
186
181
187
try :
182
188
result = cqgi .parse (plot_code ).build ()
@@ -210,11 +216,10 @@ def generate_output(self, plot_code, text, script, options):
210
216
traceback .print_exc ()
211
217
assy = Assembly (Compound .makeText ("CQGI error" , 10 , 5 ))
212
218
213
- if "side-by-side" in options :
214
- lines .append ("" )
215
- lines .append (".. raw:: html" )
216
- lines .append ("" )
217
- lines .append (" </div>" )
219
+ lines .append ("" )
220
+ lines .append (".. raw:: html" )
221
+ lines .append ("" )
222
+ lines .append (" </div>" )
218
223
lines .append ("" )
219
224
220
225
return lines
@@ -290,6 +295,74 @@ def render_image(self, assy, options):
290
295
).splitlines ()
291
296
292
297
298
+ def generate_code_block (self , code ):
299
+ """
300
+ Generate a code block with links to the documentation for method calls.
301
+ """
302
+ highlighter = theapp .builder .highlighter
303
+
304
+ body_prefixes = ["body" , "box" , "result" , "extr" ]
305
+ sketch_prefixes = ["sketch" , "s" ]
306
+ cadscript_prefixes = ["cad" , "cadscript" ]
307
+
308
+ # Regular expression to find method calls like obj.method(
309
+ # in html the string looks like
310
+ # <span class="n">cadscript</span><span class="o">.</span><span class="n">make_box</span><span class="p">(
311
+
312
+ method_call_pattern = re .compile (
313
+ r'\<span\s+class\=\"n\"\>' # <span class="n">
314
+ r'\s*(\w+)\s*' # methodName
315
+ r'\<\/span\>\s*' # </span>
316
+ r'\<span\s+class\=\"o\"\>' # <span class="o">
317
+ r'\s*\.\s*' # .
318
+ r'\<\/span\>\s*' # </span>
319
+ r'\<span\s+class\=\"n\"\>' # <span class="n">
320
+ r'\s*(\w+)\s*' # methodName
321
+ r'\<\/span\>\s*' # </span>
322
+ r'\<span\s+class\=\"p\"\>' # <span class="p">
323
+ r'\s*\(' # (
324
+ )
325
+
326
+ # Function to replace method calls with links if they are documented
327
+ # class="n">(\w+)\.(\w+)\((.*?)\)')
328
+ def replace_method_call (match ):
329
+ obj_name , method_name = match .groups ()
330
+
331
+ # Determine which document to link based on the prefix
332
+ # todo: could be done by examining env.domaindata['py']['objects']
333
+ if any (obj_name .startswith (prefix ) for prefix in body_prefixes ):
334
+ link_target = f'ref_body.html#cadscript.Body.{ method_name } '
335
+ elif any (obj_name .startswith (prefix ) for prefix in sketch_prefixes ):
336
+ link_target = f'ref_sketch.html#cadscript.Sketch.{ method_name } '
337
+ elif any (obj_name .startswith (prefix ) for prefix in cadscript_prefixes ):
338
+ link_target = f'ref_module_functions.html#cadscript.{ method_name } '
339
+ else :
340
+ return match .group (0 ) # No replacement if prefix does not match
341
+
342
+ link = (
343
+ f'<span class="n">{ obj_name } </span>'
344
+ f'<span class="o">.</span>'
345
+ f'<span class="n"><a class="codelink" href="{ link_target } ">{ method_name } </a></span>'
346
+ f'<span class="p">('
347
+ )
348
+ return link
349
+
350
+
351
+ # Use Sphinx highlighter to generate HTML
352
+ highlighted_code = highlighter .highlight_block (code , 'python' )
353
+
354
+ # Replace method calls with links
355
+ linked_code = method_call_pattern .sub (replace_method_call , highlighted_code )
356
+
357
+ lines = []
358
+ lines .append (".. raw:: html" )
359
+ lines .append ("" )
360
+ lines .extend ([" %s" % line .rstrip () for line in linked_code .split ("\n " )])
361
+ lines .append ("" )
362
+
363
+ return lines
364
+
365
+
293
366
294
367
class cadscript_auto_directive (cadscript_directive ):
295
368
@@ -351,6 +424,12 @@ def setup(app):
351
424
app .add_js_file ("vtk.js" )
352
425
app .add_js_file (None , body = rendering_code )
353
426
427
+ app .connect ("builder-inited" , remember_app )
428
+
429
+
430
+ def remember_app (app : Sphinx ) -> None :
431
+ global theapp # bad hack to remember the app object
432
+ theapp = app
354
433
355
434
356
435
if __name__ == "__main__" :
@@ -367,5 +446,5 @@ def setup(app):
367
446
setup (app )
368
447
except Exception :
369
448
pass
370
- pre_script , script , text = c .get_source_file (open (c .get_file ('./examples/bracket.py' )).read (), "2-14 " , True )
449
+ pre_script , script , text = c .get_source_file (open (c .get_file ('./examples/bracket.py' )).read (), "2" , True )
371
450
pre_script , script , text = c .get_source_file (open (c .get_file ('./examples/getting_started.py' )).read (), None , None )
0 commit comments